• python black 格式化相关的一个问题

    如实做一个记录。

    我写 Python 脚本使用 neovim + coc。lsp 使用 pyright。使用 black 做格式化。配置了保存时自动格式化。

    但是 black 不能 sort import。

    coc-python 支持 sort import,可以用户自己配置使用 pright,还是 isort。我使用的是 pyright。

    但我没找到 coc-python 里面怎么配置保存时自动 sort import。所以我在 vim 配置里面添加了一行: autocmd BufWritePre *.py :silent call CocAction('runCommand', 'editor.action.organizeImport')

    以上是前提。用了一段时间在代码格式化这块都工作良好,直到今天。

    项目中有些参数,我使用了环境变量来做配置。今天我在项目中使用 dotenv 来加载自己配置的环境变量。

    - main.py
    - utils
      - common.py
    

    项目结构大概如上这样。common.py 里面有些参数从环境变量上加载。我在 main.py 里面调用 load_dotenv。但这时候已经晚了,因为 main.py import utils.commond 在 load_dotenv 之前,这时候已经把环境变量的值取到了,还不是 load_dotenv 的内容。

    解决方法当然很简单,就是 main.py 里面把 from dotenv import load_dotenv; load_dotenv() 放最前面。但麻烦的事情来了:保存的时候 load_dotenv 被格式化到了所有 import 后面!

    中间查解决方案的过程比较长,说一下有些折腾的原因:

    black 本身可以使用 #fmt: off #fmt: on 这样的语法让一段代码不被格式化。 但是,pyright sort import 的时候并不认 black 的语法,而且它没有自己的类似这样 skip format 的功能。

    最后我还是在 coc-python 里面换成了使用 isort 来做 sort import。

    isort 的配置里面,添加 profile=black 配置可以了,这样 black 和 isort 都会认 fmt:off 这样的语法了。

    我的 isort 安装在了 /.vim/isort/bin/isort ,isort 配置也放在这里。

    # cat ~/.vim/isort/.isort.cfg
    [settings]
    profile=black
    

    main.py 代码就像下面这样

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    # fmt: off
    from dotenv import load_dotenv
    
    load_dotenv()
    # fmt: on
    
    import argparse
    import json
    
  • golang-klog

    klog

    klog 是对 glog 的永久 fork,为了对 glog 做一些改进。

    为什么又做了 klog?

    glog 已经不再活跃开发了。它有一些缺点,而且我们又需要一些新功能。所以我们只能新创建一个 klog。

    我们来说一下,glog 有哪些缺点,我们又需要什么新功能。

    1. glog 有很多 gotchas,给容器环境中使用 glog 带来挑战。

    2. glog 没有提供方便的测试方法,对使用它的软件的可靠性有影响。

    3. 我们还有一个长期目标:实现一个logging 的接口,可以允许我们添加 context,改变输出的格式等。

    glog 缺陷

    Kubernets 项目中有一个 issue 记录了glog 有哪些缺陷。Use of glog for logging is problematic,我们来看看。

    1. glog 在init 中注册了很多 flag,而且不能以编程方式配置其行为。对于 k8s 库的用户来说,他们必须调用 flag.Parse() 可能会感到惊讶,这很容易出错(例如,没有办法配置glog coredns/coredns#1597)。

    2. glog 的默认行为是将日志记录到磁盘上的文件中。一个库的用户通常不希望它在没有明确配置的情况下写入文件。

    3. 更糟糕的是,如果 glog 无法创建文件,它会调用os.Exit。这可能非常有害,特别是在使用只读根文件系统运行容器化二进制文件时,很容易触发。

    4. glog 不对其写入的文件进行任何管理,因此如果没有类似于 logrotate 的东西(特别是在容器中),日志文件将会不断积累。但是 logrotate 貌似也不容易处理 glog 的日志。

    klog 的改进

    1. 使用 klog.InitFlags(nil) 显式地初始化全局flag,因为我们不再使用 init() 方法注册 flag

    2. 现在可以使用 log_file 而不是 log_dir 来记录到单个文件

    3. 如果您想将使用klog记录的所有内容重定向到其他地方(比如syslog!),您可以使用 klog.SetOutput() 方法并提供一个io.Writer。

    4. 更多的 log 规范 Logging Conventions

  • cgroup memory limit 是不是计算 page cache

    测试环境

    OS: CentOS Linux release 7.6.1810 (Core) ; KERNEL 5.10.56 ; Cgroup: v1

    问题

    一个问题,Cgroup 下面的进程读写文件,就会生成 page cache。这部分内存使用计算到 Cgroup Memory Limit 里面吗?

    我感觉是不会的(其实会),因为如果会的话,会引出下面这个问题:

    问题二:系统怎么知道这个 page cache 属于哪个进程?

    A 读了一个文件之后,B 又读了,那这个 page cache 是同时算到 A 和 B 进程里面?

    A 读了之后,B 又使用到,那就算到 B 头上了?从 A 里面去掉?

    结果

    直接说测试下来的结果吧。

    page cache 会计算到 cgroup limit 里面。

    但内存超的时候,不会直接触发 OOM Killer,而是会清理部分 page cache。

    page cache 始终算在第一个使用它的进程(以及对应的 Cgroup)上。

    清理策略

    多提一句,cgroup v2 的清理 page cache 策略应该比 v1 有优化,参考如下:

    Another important topic in cgroup v2, which was unachievable with the previous v1, is a proper way of tracking Page Cache IO writebacks. The v1 can’t understand which memory cgroup generates disk IOPS and therefore, it incorrectly tracks and limits disk operations. Fortunately, the new v2 version fixes these issues. It already provides a bunch of new features which can help with Page Cache writeback.

    https://biriukov.dev/docs/page-cache/6-cgroup-v2-and-page-cache/

    验证

    准备工作

    创建几个文件待用

    # dd if=/dev/random of=1 bs=1000000 count=500
    500+0 records in
    500+0 records out
    500000000 bytes (500 MB) copied, 3.03696 s, 165 MB/s
    [root@test-page-cache]# for i in {1..6} ; do dd if=/dev/random of=$i bs=1000000 count=500 ; done
    500+0 records in
    500+0 records out
    500000000 bytes (500 MB) copied, 3.13473 s, 160 MB/s
    500+0 records in
    500+0 records out
    500000000 bytes (500 MB) copied, 3.06832 s, 163 MB/s
    500+0 records in
    500+0 records out
    500000000 bytes (500 MB) copied, 3.04262 s, 164 MB/s
    500+0 records in
    500+0 records out
    500000000 bytes (500 MB) copied, 3.03191 s, 165 MB/s
    500+0 records in
    500+0 records out
    500000000 bytes (500 MB) copied, 3.04711 s, 164 MB/s
    500+0 records in
    500+0 records out
    500000000 bytes (500 MB) copied, 3.07169 s, 163 MB/s
    

    清理一下page cache

    [root@test-page-cache]# free -h
                  total        used        free      shared  buff/cache   available
    Mem:            22G        5.8G        2.8G        365M         14G         16G
    Swap:          8.0G         16M        8.0G
    [root@test-page-cache]# echo 3 > /proc/sys/vm/drop_caches
    [root@test-page-cache]# free -h
                  total        used        free      shared  buff/cache   available
    Mem:            22G        5.3G         16G        365M        1.2G         16G
    Swap:          8.0G         16M        8.0G
    

    启动一个带 memory limit 的 docker 容器

    docker run --rm -ti -v $PWD:/tmp/test-page-cache -w /tmp/test-page-cache --memory 1G alpine:3.16.2
    

    测试1

    测试1,是想验证一下 page cache 算在 limit 里面吗,以及触发了 limit 会怎么样?

    读取两个之前生成的文件(每个文件500MB)

    /tmp/test-page-cache # cat 1 > /dev/null
    /tmp/test-page-cache # cat 2 > /dev/null
    

    看一下 page cache 情况

    [root@89e16b39fdc6da0f6c0e4d936aa0df8141ebe20d033fd8a7540ad0d550d8163c]# free -h
                  total        used        free      shared  buff/cache   available
    Mem:            22G        5.3G         15G        365M        2.6G         16G
    Swap:          8.0G         16M        8.0G
    
    # pwd
    /sys/fs/cgroup/memory/docker/89e16b39fdc6da0f6c0e4d936aa0df8141ebe20d033fd8a7540ad0d550d8163c
    # cat memory.usage_in_bytes
    1073512448
    # cat memory.stat
    cache 1069584384
    rss 106496
    rss_huge 0
    shmem 0
    mapped_file 0
    dirty 0
    writeback 0
    swap 0
    pgpgin 488796
    pgpgout 227651
    pgfault 924
    pgmajfault 0
    inactive_anon 102400
    active_anon 0
    inactive_file 1069461504
    active_file 0
    unevictable 0
    hierarchical_memory_limit 1073741824
    hierarchical_memsw_limit 2147483648
    total_cache 1069584384
    total_rss 106496
    total_rss_huge 0
    total_shmem 0
    total_mapped_file 0
    total_dirty 0
    total_writeback 0
    total_swap 0
    total_pgpgin 488796
    total_pgpgout 227651
    total_pgfault 924
    total_pgmajfault 0
    total_inactive_anon 102400
    total_active_anon 0
    total_inactive_file 1069461504
    total_active_file 0
    total_unevictable 0
    

    在容器里面继续读文件

    /tmp/test-page-cache # for i in 1 2 3 4 5 6 ; do echo $i ; cat $i > /dev/null ; done
    1
    2
    3
    4
    5
    6
    /tmp/test-page-cache #
    

    可以看到 page cache 不再增加了

    # free -h
                  total        used        free      shared  buff/cache   available
    Mem:            22G        5.3G         14G        365M        2.7G         16G
    Swap:          8.0G         16M        8.0G
    # cat memory.stat
    cache 1067421696
    rss 241664
    rss_huge 0
    shmem 0
    mapped_file 0
    dirty 0
    writeback 0
    swap 0
    pgpgin 1099560
    pgpgout 838969
    pgfault 1617
    pgmajfault 0
    inactive_anon 372736
    active_anon 0
    inactive_file 1067384832
    active_file 0
    unevictable 0
    hierarchical_memory_limit 1073741824
    hierarchical_memsw_limit 2147483648
    total_cache 1067421696
    total_rss 241664
    total_rss_huge 0
    total_shmem 0
    total_mapped_file 0
    total_dirty 0
    total_writeback 0
    total_swap 0
    total_pgpgin 1099560
    total_pgpgout 838969
    total_pgfault 1617
    total_pgmajfault 0
    total_inactive_anon 372736
    total_active_anon 0
    total_inactive_file 1067384832
    total_active_file 0
    total_unevictable 0
    # cat memory.usage_in_bytes
    1073528832
    

    使用 vmtouch 这样的工具,应该可以看到前一个文件的 cache 被清掉了。不方便安装,就不再继续验证了。

    测试2

    测试2是验证一下,page cache 算在第一个进程里面。其实就是说,如果文件已经在 page cache 里面了,再次 read 不会增加本进程所在cgroup 的 cache 值。

    先在宿主机上面清理一下 page cache,准备一个干净的环境。然后在宿主机在 cat 一个文件,缓存到 page cache 里面。

    # echo 3 > /proc/sys/vm/drop_caches
    # free -h
                  total        used        free      shared  buff/cache   available
    Mem:            22G        5.3G         16G        365M        1.2G         16G
    Swap:          8.0G         16M        8.0G
    [root@VMS172906 ~]# cd /tmp/test-page-cache/
    # cat 1 > /dev/null
    

    然后在容器里面,再次 cat 1,可以看到容器的内存使用没有增加。

    # cat memory.stat
    cache 675840
    rss 241664
    rss_huge 0
    shmem 0
    mapped_file 0
    dirty 0
    writeback 0
    swap 0
    pgpgin 1100286
    pgpgout 1100138
    pgfault 2673
    pgmajfault 0
    inactive_anon 507904
    active_anon 0
    inactive_file 520192
    active_file 0
    unevictable 0
    hierarchical_memory_limit 1073741824
    hierarchical_memsw_limit 2147483648
    total_cache 675840
    total_rss 241664
    total_rss_huge 0
    total_shmem 0
    total_mapped_file 0
    total_dirty 0
    total_writeback 0
    total_swap 0
    total_pgpgin 1100286
    total_pgpgout 1100138
    total_pgfault 2673
    total_pgmajfault 0
    total_inactive_anon 507904
    total_active_anon 0
    total_inactive_file 520192
    total_active_file 0
    total_unevictable 0
    
  • golang gc

    https://tip.golang.org/doc/gc-guide

    https://github.com/golang/go/issues/30333

  • 根证书验证中的一点细节

    很多时候,公司内部会使用私有证书。我们把私有根证书下载到本地并信任,就可以访问公司内的 HTTPS 域名了。

    那具体一点说,我们访问 HTTPS 域名的时候,网站会把域名的证书推送给我们(一般来说是浏览器,或者是 curl 命令等),这个证书里面包含了 CN 以及公钥等信息。同时还包含证书链 – 中间证书直到根证书。链上的证书的信息是不完整的,比如说,根证书里面可能有 CN 信息,但没有公钥数据。

    那么问题来了,我怎么知道这个证书需要使用哪个根证书去验证呢?通过对比 CN 吗?还是别的什么字段?

    如果你知道这个答案,可以不用看下去了。如果不知道,也可以直接翻到最后一节看。

    不好!出问题了!

    突然接到隔壁组同事的电话,说他们访问我这边的一个应用,app.corp.com,报了证书过期的错误。

    我第一反应是,这个域名大概在半年前证书过期了,导致了一次事故。难不成是又过期了?

    马上打开电脑,打开网站,正常访问,查看证书信息,也没看到什么异常。(注:其实应该是有异常的,可能是 Edge 浏览器忽略了这个异常?后面换浏览器打不开网站 。)

    但同事给的报错截图上面写的清清楚楚,“证书过期”。

    然后登陆到一台服务器上面,curl -v https://app.corp.com,发现果然有问题。上面显示 issuer’s certificate 过期。

    我们的域名证书是由根证书直接签发的,难道说根证书过期了?

    一直以来的理解都不对

    一方面庆幸,还好不是我的锅了,另外一方面又想,如果根证书过期,事情就搞大了吧?

    因为一直以来我对根证书的理解是这样的:

    开天辟地之时,公司先创建一个私钥和公钥,然后拿私钥公钥做出来一个根证书 CA。这个 CA 呢,一方面用来签发其他子域名的证书,另外一方面,需要分发到每一台服务器以及员工的办公电脑上面。

    按我这个理解的话,如果根证书过期了。就需要拿私钥公钥再重新做一个根证书,然后给每个子域名再签发证书,同时还要把新的根证书分发到每一台服务器以及员工办公电脑上。

    这个工作量有些大。

    并非如此

    我们联系了 SRE 的同事。他们说是需要下载一个新的根证书,并更新到出问题的服务器,也就是调用我们服务的那个同事的服务器上,就可以了。(并不需要什么再签发子域名的证书。)

    测下来也的确如此。

    那么问题就来了,app.corp.com 传过来的证书,是怎么对应到新下载的根证书的呢?

    Golang 代码答疑

    在网上搜索了一下,并没有找到答案(可能是我的关键字给的不对)。不过一切都在代码里,去翻翻看代码吧。

    Golang 的 tls 库里面有一个代码示例 https://pkg.go.dev/crypto/tls#example-Dial。从这里 debug 进去,仔细查看,终于明白了这其中机制。

    简单来说,golang 会遍历所有的根证书,看看用根证书能不能解码 app.corp.com 的加密信息。如果可以,那就是我们要找的根证书。然后再看这个根证书是不是过期啊,等等。

    再稍微多说一些细节呢。

    1. golang 会首先会根证书做一个排序,优先验证更可能的根证书,比如说 CN 一样的。
    2. golang 不会验证所有的根证书,这里面有一个硬编码,最多验证100个。
    3. golang 验证到合法根证书之后,不会停下来,会尝试拿所有的证书链信息做后续的使用(这应该是一个 golang 可以改进的点)。黑盒测试的结果,curl 会停下来。也就是说,如果我们电脑上没有合法的根证书,客户端在根证书验证这里耗时会增加不少。
  • 为什么 rsyslog 把 etcd 日志采集到了 kernel.log

    etcd版本:

    etcd Version: 3.4.9
    Git SHA: 54ba95891
    Go Version: go1.12.17
    Go OS/Arch: linux/amd64
    

    经测试,rsyslog 的下面这个配置导致日志采集到了 kernel.log

    #### RULES ####
    
    # Log all kernel messages to the console.
    # Logging much else clutters up the screen.
    kern.*                                                  /var/log/kernel
    

    rsyslogd 版本

    rsyslogd 8.29.0, compiled with:
    	PLATFORM:				x86_64-redhat-linux-gnu
    	PLATFORM (lsb_release -d):
    	FEATURE_REGEXP:				Yes
    	GSSAPI Kerberos 5 support:		No
    	FEATURE_DEBUG (debug build, slow code):	No
    	32bit Atomic operations supported:	Yes
    	64bit Atomic operations supported:	Yes
    	memory allocator:			system default
    	Runtime Instrumentation (slow code):	No
    	uuid support:				Yes
    	Number of Bits in RainerScript integers: 64
    
    See http://www.rsyslog.com for more information.
    

    ETCD 在 journald 中的一条日志如下:

    {
    	"__CURSOR" : "s=78fdc2bf435b4aa6b7df9f50ff1e9c9f;i=662c5390;b=f3260b93a64641889bbf8fed67f4365a;m=2f4d3ea8e9fc;t=5ee329ba40462;x=82eb53b68142dca7",
    	"__REALTIME_TIMESTAMP" : "1669276010546274",
    	"__MONOTONIC_TIMESTAMP" : "52008810244604",
    	"_BOOT_ID" : "f3260b93a64641889bbf8fed67f4365a",
    	"PRIORITY" : "7",
    	"SYSLOG_IDENTIFIER" : "etcd",
    	"_TRANSPORT" : "journal",
    	"_PID" : "840",
    	"_UID" : "997",
    	"_GID" : "993",
    	"_COMM" : "etcd",
    	"_EXE" : "/usr/local/bin/etcd",
    	"_CMDLINE" : "/usr/local/bin/etcd --config-file /etc/etcd/etcd.conf.yml --log-output stderr",
    	"_CAP_EFFECTIVE" : "0",
    	"_SYSTEMD_CGROUP" : "/system.slice/etcd.service",
    	"_SYSTEMD_UNIT" : "etcd.service",
    	"_SYSTEMD_SLICE" : "system.slice",
    	"_MACHINE_ID" : "c94b645006e94b62b253832779707d12",
    	"_HOSTNAME" : "SVR15178IN5112",
    	"PACKAGE" : "etcdserver/api/v3rpc",
    	"MESSAGE" : "start time = 2022-11-24 15:46:50.545251478 +0800 CST m=+51575280.245951347, time spent = 821.324\uffffffc2\uffffffb5s, remote = 10.4.241.133:54952, response type = /etcdserverpb.KV/Txn, request count = 0, request size = 0, response count = 0, response size = 31, request content = compare:<key:\"cilium/state/identities/v1/id/292984\" version:0 > success:<request_put:<key:\"cilium/state/identities/v1/id/292984\" value_size:196 >> failure:<>",
    	"_SOURCE_REALTIME_TIMESTAMP" : "1669276010546119"
    }
    

    粗看下来,是因为 etcd 使用一些库记录日志到 journald 的时候,没有加 FACILITY 字段。

    rsyslog 采集日志的时候,会通过 PRIORITY » 3 的方式计算 FACILITY。计算结果为0,认为FACILITY 是 kernel。

    Rsyslog 的一些 const value: https://github.com/rsyslog/rsyslog/blob/d083a2a2c20df6852a53e45f1e7a3f47679236d6/runtime/rsyslog.h#L202

    #define	LOG_KERN	(0<<3)	/* kernel messages */
    #define	LOG_USER	(1<<3)	/* random user-level messages */
    #define	LOG_MAIL	(2<<3)	/* mail system */
    #define	LOG_DAEMON	(3<<3)	/* system daemons */
    #define	LOG_AUTH	(4<<3)	/* security/authorization messages */
    #define	LOG_SYSLOG	(5<<3)	/* messages generated internally by syslogd */
    #define	LOG_LPR		(6<<3)	/* line printer subsystem */
    #define	LOG_NEWS	(7<<3)	/* network news subsystem */
    #define	LOG_UUCP	(8<<3)	/* UUCP subsystem */
    

    Rsyslog 计算 FACILITY 的宏 https://github.com/rsyslog/rsyslog/blob/d083a2a2c20df6852a53e45f1e7a3f47679236d6/runtime/rsyslog.h#L251

    pri2fac(const syslog_pri_t pri)
    {
    	unsigned fac = pri >> 3;
    	return (fac > 23) ? LOG_FAC_INVLD : fac;
    }
    
  • [译]Linux下 OOMKiller 什么时候被触发

    原文Roughly when the Linux Out-Of-Memory killer triggers (as of mid-2019)

    原文发表时间,2019-08-11

    因为某些别的原因,我最近想了解 Linux 里面 OOM Killer 是何时触发(以及不触发)的,以及为什么。这方面的详细文档不多,以及有些已经过时了。我在这里也没办法添加详细文档,因为这需要对 Linux 内核代码很了解,但我至少可以大概写些观点供我自己使用。

    现如今有两种不同类型的 OOM Killer:全局的 OOM Killer 和 依赖 cgroup 的 OOM Killer(后者通过cgroup 内存控制器实现)。我主要是对全局的感兴趣,部分是因为 cgroup OOM Killer 相对来说容易预测。

    简单来说,当内核在分配物理内存页有有困难时,全局 OOM Killer 触发。 当内核尝试分配内存页(不管用于任何用途,用于内核使用或需要内存页的进程)并且失败时,它将尝试各种方法来回收和压缩内存。如果成功了或至少取得了一些进展,内核会继续重试分配(我从代码中了解到); 如果他们未能释放内存页或取得进展,它会在许多(但不是全部)情况下触发 OOM Killer。

    (比如说,内核申请连续大段内存页失败,是不会触发的,参考Decoding the Linux kernel’s page allocation failure messages。如果申请的连续内存小于 等于 30KB 才会触发。git blame 显示从2007年就开始是这样的了。)

  • [译]SO_REUSEPORT选项

    原文 The SO_REUSEPORT socket option [LWN.net]

    One of the features merged in the 3.9 development cycle was TCP and UDP support for the SO_REUSEPORT socket option; that support was implemented in a series of patches by Tom Herbert. The new socket option allows multiple sockets on the same host to bind to the same port, and is intended to improve the performance of multithreaded network server applications running on top of multicore systems.

    Linux 3.9 版本合并了一个 SO_REUSEPORT 的特性。可以在 TCP 和 UDP 的套接字上面配置 SO_REUSEPORT 这个选项;这项支持是 Tom Herbert 在一系列的补丁中实现的。这个新的套接字参数可以允许一台机器上面将多个套接字绑定在同一个端口上面,目的是为了提高多核机器上面的多线程网络服务应用的性能。

  • kafka-lib fetch api的数据解析

  • Journald Crash

    昨天和芽哥一起查了一个 ETCD 切主的问题,挺有意思的,记录一下。

    最终查明了根因,是使用 filebeat 采集 Journal 日志,引发 journald flush,导致高磁盘 IO,进而导致 ETCD 切主。

  • 1
  • 2