Author Archives: admin

supervisor泄漏进程案例分析

起因 前几天使用 salt ‘*’ test.ping 的时候发现响应内容中有一些“某某minion was already deleted from tracker, probably a duplicate key“的提示信息。刚开始误以为是salt-key管理有问题,尝试删除再重新accept,但是依然会出错。到该minion上检查,发现上面运行了两套salt-minion*三层进程树,一共6个进程,其中一套的PPID为1,另一套的Parent是supervisord。 然后就开始研究这种情况是怎么产生的,发现有两种可能: 第一种可能 supervisor本身不被systemd监管,被SIGKILL信号杀死时,因为SIGKILL由内核直接处理,所以并没有机会关闭下属的进程,导致下属salt-minion进程树泄漏。而且不但salt-minion进程树泄漏,连同样被supervisor监管的另一个服务也一并泄漏,二者的PPID都变成了1号。 不过,如果supervisor本身被systemd监管,在其主进程被杀死时,systemd会给整个service slice cgroup里所有进程补刀,所以并不会泄漏进程;如果supervisor是被SIGTERM信号杀死,它也会给下属子进程发信号,一般也不会泄漏进程。 第二种可能 supervisor没有受到影响,正常运行;supervisor监管的salt-minion三层进程树的其中最高层进程(也就是supervisord的直属子进程)被SIGKILL信号杀死,随即,第二层进程exit(1) (不明原因,可能需要看一下salt-minion源码),导致第三层进程变成孤儿。经检查源代码的_spawn_as_child()函数,supervisor针对其监管下的每一个服务,都是采用 fork() + setpgid() +execve() 的方式来启动的,在调用setpgid()改变了process group id之后,第三层进程的孤儿收养关系就不再归属于supervisord进程,而是归属于1号进程。 随后supervisor会重启salt-minion服务,产生新的3个进程,加上之前剩下的,一共4个。 结论 考虑到观察到6个进程而不是4个,实际发生的大概是前一种情况 supervisor虽然有“能力”处理进程退出之后马上重启的工作,但是因为使用了setpgid()把下属服务与自己隔离,没使用cgroup机制把下属服务单独圈起来,又不具备1号的神圣地位,其实它并不知道到底下属了多少、哪些进程,从机制原理上就根本无法保证所有下属的孤儿进程都被其reap。还是建议不要在严肃场合使用 1号进程神圣,所有的服务进程监管工作都应该交给1号进程来处理

Posted in 默认分类 | Tagged , , | Leave a comment

Reproducible builds 果然是很重要的

去年10月17日公司出了个事故,“幸好有我“,力挽狂澜。我觉得还是把具体内容脱敏,记录一下吧,免得将来忘了。 早晨test环境某个自研产品组件build成功,但是运行时错误日志里一堆“Broker hostname updated”等等,仿佛孔乙己说的话,让人听不懂。我刚开始没把这个当回事,请Kafka管理员去检查,几个小时都没查出原因来。 到了午后,各自研组件纷纷开始更新Live环境的版本,发布的都是前几天在Test环境已经测试定稿的版本,居然也出现了类似的问题。更狠的是,回滚居然也无效! 最终查实,有不少自研产品的build脚本里,使用wget下载master版本的librdkafka然后编译使用。而librdkafka恰在前一天(10月16日)被提交了很多修改。当时紧急解决的方法是使用librdkafka的上一个被明确标记了tag的v1.2.1版本代替master版本。 事后复盘处理过程,觉得当时的处理过程也有问题:所谓回滚其实并非直接使用旧版本编译出来的docker image,而是重新build了旧版本,而且编译时指定的是branch名字,而不是精确的commit id,这样的问题是无论外部代码(master版本的librdkafka)还是自研代码(注意branch有可能move forward哦),都不一定是原来的版本,这个行为其实并不能称作回滚,而是带有主观回滚意向的一次重新构建。 在这种情况下,讨论自研代码和外部库的质量,都是缺乏讨论基础的。这种讨论应该基于确定的版本。 说实话,我以前对于传说中的“Google把自研代码、外部库和工具链”全都纳入版本化管理,是有点嗤之以鼻的。当时我觉得这种做法,在处理多个自研软件的外部依赖相互冲突时,会带来额外的行政成本,在国内企业的KPI风格下,会导致相互推诿。经过这次力挽狂澜之后,觉得相互推诿其实是KPI至上主义导致的,而不是把所有代码都管起来导致的;不过是否真的要管这么大范围,尚有待商榷。

Posted in 默认分类 | Tagged , | Leave a comment

广东电信IPTV 华为EC6108v9c 的使用经验

前天(3月9日)终于拨乱反正把还剩下近一个月但网络“几乎能通”的垃圾天威视讯给弃用了,换了正经运营商中国电信广东深圳公司的上网服务,并且开通了IPTV。因为今年租的房子是正经居民房,就遭遇到了经典的“弱电箱距离电视机太远”的问题。 本来IPTV盒子可以支持Wi-Fi的,但它很快就强制自动升级,把本来支持的Wi-Fi联网方式给去掉了。 网上找到一个固件,保留了“粤TV”app,并且重新开启了Wi-Fi功能,且开放安装外来安卓软件。刷新说明里写着放在FAT32的U盘里,文件名叫update.zip,开盒子之后在bootloader过程中按左右键进入recovery,然后apply update from external storage即可。但我照做的时候,recovery提示没有找到升级包。 再搜,发现还有一个所谓按待机键进入recovery的,但我按不出来。妄want图to 拆盒子短接跳线,还从盒子里掉出一个虫子尸体,算是给祖师爷致敬了。 我想了想,可能这俩方法是不同时期的。于是我在当前recovery里apply update from backup刷回旧版了,(中间还操作了一步wipe dalvik cache,清除新版的升级包,避免旧版开机之强制刷新新版)然后再按待机键进入旧版recovery,刷了这个update.zip进去。 再说网络。官版固件有线网接入,目前是IPoE(其实就是特殊的DHCP)或者PPPoE认证的;直接DHCP无法获得IP地址。我改了一下光猫,把原来的VLAN 45上联业务,从PPPoE桥接模式(即:客户端自己PPPoE)改为PPPoE路由模式,并开放下行DHCP服务,再关联到SSID1上,用IPTV盒子去连这个Wi-Fi即可。理想情况下应该开多个SSID,但我这个光猫似乎不支持多个,所以这个2.4G only的Wi-Fi就给IPTV用,而自家其他通用设备使用的Wi-Fi,用了额外一个5GHz路由器从有线网口转出来。

Posted in 默认分类 | Tagged , | 1 Comment

经典错误——使用/etc/security/limits.conf配置文件 和 ulimit -n命令

很多以讹传讹的半桶水文章,都教人修改/etc/security/limits.conf配置文件来放宽“打开的文件数量”限制,如果可以再多一滴水的话,还会加一句“重启后生效”。 其实,使用这个配置文件,和使用ulimit -n命令一样,属于很经典的错误。 设置或放宽“打开的文件数量“限制,其本质是调用了setrlimit()函数,设置了RLIMIT_NOFILE资源。在有特权的程序中调用这个函数,可以提高上限(放宽限制),而普通权限的程序只能自己勒死自己和新生的子进程。 而/etc/security/limits.conf这个配置文件是怎么生效的呢?其实用dpkg -S或rpm -qf查一下就很容易知道,这个文件是pam_limits.so的配置文件,而pam_limits.so是在/etc/pam.d/中被login和sshd等多个配置文件声明将要被调用的。 系统开机的时候,1号进程init“自然而然”是root身份运行,其下属的getty/login和sshd进程,也都是root身份。这些程序都可以随意调用setrlimit。当身份认证(部分工作由PAM来做,所以可以读shadow文件)完成之后,login和sshd的子进程会为用户准备好session(网络登录调用pam_mkhomdir建设HOME目录、pam_limits模块设置rlimit、pam_env模块读取/etc/environment设置环境变量,甚至显示motd这种功能也是PAM模块实现的)并将自己降级到登录的用户身份,再启动一个shell给用户使用。 /etc/security/limits.conf 只对“调用过pam_limits.so“的登录过程有效。但并不是所有场景都经过这个过程的。而ulimit命令呢,它本身只是shell是一个内部命令而已,只对“该shell进程”及随后新产生的子进程有效。 但是需要放宽rlimit的程序,往往不是在shell中由用户手工运行的程序,而是提供大规模网络服务的后台进程。它们所需的rlimit,要在init脚本、service unit文件中设置;支持从root身份启动的服务,一般都有自行设置rlimit的能力。 如果不理解上面的内容,就容易引发一些莫名其妙的故障。比如之前我在FreeWheel工作的时候,前辈为后台服务写的的init脚本里没有调用ulimit -n命令,而在root用户的~/.bash_profile里有这个命令。造成的后果,就是开机自动启动该服务的时候,启动的是一个打开文件数量受限,以至于无法保持很多socket的网络服务,而当运维人员登录进去手工重启服务之后,又莫名其妙变好了,以至于没法检查这个故障到底是怎么发生的。

Posted in 默认分类 | Tagged , | Leave a comment

从神州租车更改手机号,看数据库关系设计

我搬来深圳之后,就换了手机号。为了应对一些出行需求,我就打算从神州租车租辆车来用。用新手机号注册之后提交身份证,结果提示该身份证已经被占用。联系客服,说是一个135的北京手机号占住了,还提示我以前在嘉峪关租过车,客服可以帮忙把这个135的登录手机号改成我的新手机号。我检查了一下通讯录,认识,跟他打了招呼之后就同意了客服的做法。 但其实,这个事情暴露了神州租车方面的数据库强关系设计,与实际业务冲突的问题。我和这位朋友之前两次一起租车,因为他当时还不会开车,所以登记了我的证件,但后续他仍有使用此注册名租车的业务需求啊。身份信息应该关联在“一次租车行为”而不是关联在“一个注册用户”身上,也就是每次发生租车行为的时候,把身份信息从注册信息复制过来,或者由门店人工录入身份信息到“一次租车行为”的数据库记录里。 这不是关系范式之间来回迁移,而是正确与错误的区别。 联想到以前在美团我们自制的一个用作软件版本发布的小系统,旧的设计并没有把“发布功能相关设置”关联到“一次发布行为”,而是关联到“要发布的应用”,以至于检查每一次发布行为到底为啥不正确的时候,根本不敢确定设置页面里看到的设置就是当时生效的设置。 这个错误在后面重构中被推翻了。

Posted in 默认分类 | 1 Comment

在嵌入式开发板上使用蓝牙耳机

简单记录一下: 安装 bluez pulseaudio-module-bluetooth 软件包 pulseaudio是用户级后台服务,视情况可能需要手工-D启动之 bluetoothctl命令进入交互式界面,先scan on 等看到蓝牙耳机之后,pair和connect到蓝牙耳机的MAC地址,看到连接成功之后scan off并退出bluetoothctl pactl list sinks查看设备,看看它支持哪些profile,有可能需要通过pactl set-card-profile将其切换到a2dp aplay -Dpulse 指定pulse虚拟设备播放文件 还没搞定录音。看起来话务耳机在handfree等模式似乎需要特殊步骤激活录音模式?

Posted in 默认分类 | Tagged , | Comments Off on 在嵌入式开发板上使用蓝牙耳机

placeholder 2016年10月欧洲之行

6月办签证,因为英国当时在闹脱欧,签证审批速度大大降低,耽误了办理申根,连瑞航机票都耽误了。所以决定10月辞职后再去。 临辞职,拿了在职证明去办法国申根,但忘记提前买好英法之间欧洲之星国际列车的票,后来多花了好多钱 走之前没查时区,10天行程调了5次时区,痛不欲生 不喜欢法国的繁复建筑物,但很爱她的博物馆们;塞纳河左岸下午照不到太阳,是酸臭文人吹捧起来的地方。 英国很有沉淀感 国际标准时间曾经是巴黎天文台,但英国人解决精度问题之后,标准时间变成了“比我巴黎时间晚9分9秒的‘那个时间’”

Posted in 默认分类 | Tagged , , | Leave a comment

django database router是个好东西

今天给sentry加了MySQL读写分离机制,记录一下: DATABASES里,(sentry的脾气比较怪)保留名为default的配置,写master数据库的参数;新增一个名为slave的配置,使用只读用户名密码,或开启服务器端read only 然后增加一个类,带四个函数: class DatabaseRWSplitRouter(object): def db_for_read(self, model, **hints): return ‘slave’ def db_for_write(self, model, **hints): return ‘default’ def allow_relation(self, model1, model2, **hints): return True def allow_migrate(self, db, app_label, model_name=None, **hints): if db == ‘slave’: return None else: return True … Continue reading

Posted in 默认分类 | Tagged , , | Leave a comment

内训教程释出:from CGI to WSGI

古代时,读过MSDN里的《from C++ to COM》受益匪浅。后来学习Python,自己也写了一篇 from CGI to WSGI 的教程,用于内部培训。 https://julyclyde.org/pyCGIWSGI.pdf

Posted in 默认分类 | Tagged | Leave a comment

给rq添加了RedisCluster支持

最近一直在和前同事Puff潘、RainSun吕合作,做一些给使用redis的软件增加redis cluster或者redis sentinel支持功能的工作。 传统上,做sharding工作有三种做法: 客户端支持,比如redis cluster客户端需要支持KeySlot计算(我报告的bug: https://github.com/Grokzen/redis-py-cluster/issues/153) 代理层支持:twemproxy、codis等 服务器端支持:比如MySQL partition table之类 我比较推崇的是第二种,但redis的cluster和sentinel都属于第一种,客户端需要有明确的能力和知识去处理连接多个服务器的问题,而这方面,各常见客户端库做的并不好。另外还有个问题是用Python语言写的各应用软件,往往直接写死了用redis库,而不是用rediscluster库或者运行期动态决定,导致根本不具备使用cluster的能力。 近期在看rq的时候,发现rq支持custom worker class,于是我就改了改,让它也支持custom connection class,并说服了作者merge进来。 https://github.com/nvie/rq/pull/741 不过其实我觉得还是略别扭。主要是因为生成connection对象的时候套了两层函数,为了补全function signature,并尽量减少其它代码的修改,不得不在两层都使用参数默认值,显得多余。尽管如此,还是很为自己能为基础软件做出贡献而感到高兴的。

Posted in 默认分类 | Leave a comment