笔者负责的服务有一个设计是通过 pipe 在父子进程间传输请求。在物理机上时,父子进程能够正常完成 request 和 response 。但是在上 k8s 后,发现:
如果把服务用 docker 的方式部署又会没有这个问题。简而言之,就是一个非常神奇而又让人抓狂的问题。
此问题由两方面导致的:
上 K8s 后,在 pod 内的 pipe 大小,被限制为只能写入 4096 字节
宿主机的 pipe 大小
容器内的 pipe 大小
服务自身设计问题导致:
笔者负责的服务父进程的 request 的包的大小已经超过了 4096 字节
父进程 write 的时候,使用的非阻塞 write,上 k8s 后写入 pipe 的 buffer 大小为 4096 字节后 buffer 就满了,会到导致写入的数据不完整
本着「知其然,还有知其所以然」的精神,先从修改配置「CAP_SYS_RESOURCE」的历史说起吧。
注:CAP_SYS_RESOURCE,忽略资源限制
内核从 2.2 开始,Linux 将传统上与超级用户 root 关联的特权划分为不同的单元,称为 CAP。CAP 作为线程的属性存在,每个单元可以独立启用和禁用。
注:Linux 并不真正区分进程和线程
每个进程有三个和能力有关的位图:inheritable(I)、permitted§ 和 effective(E),对应进程描述符 task_struct(include/linux/sched.h) 里的 cap_effective、cap_inheritable、cap_permitted ,所以可以查看 /proc/{PID}/status 来查看进程的能力。
注:笔者之前在物理机上部署的方式是使用特权命令 docker run --privileged,此命令可以继承宿主机大部分的控制权限,有一定的风险性
通过查看 /proc/{pid}/status ,然后过滤其中的 Cap 字段属性:
注:通过查看 pipe size 的 shell 工具,可以看到当前 00000000000c25fb 是这个值时,pipe 的大小为 4096B
pipe size 检验工具: M=0; while printf A; do >&2 printf “\r$((++M)) B”; done | sleep 999
注:注:通过查看 pipe size 的 shell 工具,可以看到当前 00000000010c25fb 是这个值时,pipe 的大小为 65536B
capsh 命令可以将下面两个能力位图转义为可读的格式:
00000000000c25fb -> 4096B
00000000010c25fb -> 65536B
至此真相大白,需要为 pod 增加 CAP_SYS_RESOURCE 的能力。
要为容器添加或删除 Linux 功能,请在容器的 securityContext 部分中包含 capabilities 字段,详细的 Linux capabilities 见 Linux capabilities:
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: demo
image: centos:7
command: ["tail","-f", "/dev/null"]
securityContext:
capabilities:
add: ["SYS_RESOUCE"]
详细的增加参考见:Set capabilities for a Container
最近真的是忙的各种心力交瘁,想抽出点时间学习新知识都感觉有写困难,希望下个月能够规划好工作和生活。「放弃当然很容易,但是坚持就会很酷吖」