经典错误——使用/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

配置管理 vs provisioning 及配置管理工具的几点随想

关于“怎么构建一个确定的运行环境”这件事,有多个流派,其中一个是配置管理,另一个是provision流。

配置管理流派,适合于物理服务器、虚拟机等等,有机会长期存活的环境。因为它们有机会长期存活,在其生命周期里需要使用技术手段去维持一个有序可控的状态,排除软件运行时累计的旧日志和临时数据、手工操作带来的计划外变更等等影响。

而provision流派,直接从模版构建一个(容器或虚拟机)实例起来运行,用完就扔,基本上没有重新调整的价值。这种东西往往依赖于服务注册与发现机制,因为在它真正起来运行之前,网络通信方面的参数无法被外界预知,外界不知道如何跟它通信;对于日志和数据,也提倡使用显式远程的方式去访问。

 

再说说配置管理工具的几点随想:

我最近一年在给下属的一个公司做一些产品运维工作,其中遇到把设备投放到客户的网络环境去运行这种情况。考虑到网络通信的问题,我只好选择了“反向连接”的saltstack软件。在通信的角度来考虑,配置管理工具可以分为:master主动连接minion(ansible等)、minion主动连接master(puppet、saltstack等)

今天听师兄说他的一个同事因为认知问题,把一批机器glibc给删掉了。尝试恢复的时候,他发现ansible无法正常运行(我猜想sshd启动python的时候因为缺glibc而失败了),只好改用saltstack。因为saltstack的salt-minion是长期运行的,一旦启动之后,外部依赖较少,才能在glibc被删除这么极端的条件下苟活。在“有没有agent”的角度考虑,配置管理工具可以分为:有agent(saltstack、puppet、cfengine等)和无agent(ansible等)

另外,其实还有一个分类角度,就是主动和被动。saltstack和ansible是主动式的,运维工程师可有更多的主动权,可以用手工指定minion,或者指定批次规模分批执行等手段,控制变更的节奏;cfengine、puppet等是agent定时刷新式的(虽然听说也可以主动?不过我经验较少还没用过),得按定时器来,这种情况下就得运维工程师多看监控了,一不小心,死都不知道怎么死的。

Posted in 默认分类 | Tagged | Leave a 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 在嵌入式开发板上使用蓝牙耳机

hostname和dnsdomainname命令

先讲结论:

忽略已经过时的NIS/YP相关内容(/sbin/sysctl kernel.domainname、/bin/domainname、/bin/nisdomainname、/bin/ypdomainname等)

/bin/hostname、/sbin/sysctl kernel.hostname 和 /bin/uname -n 是一码事,都是本机的主机名

/bin/dnsdomainname 命令会把上述主机名按“第一个点”分成两端,输出后一段。这是简单的字符串处理的结果,在内核和DNS层面均无正式意义。

/etc/resolv.conf文件里的search或domain指令,用于从本机访问外部短主机名时,补充域名的后缀部分。

 

hostname命令:

通过gethostname(2)函数读到本机的主机名。在glibc的情况下,gethostname(2)不是syscall而是标准库函数,转而调用uname(2) (我不太确认调用的是glibc uname还是syscall uname)。根据 https://github.com/torvalds/linux/blob/master/Documentation/sysctl/kernel.txt#L285 的说法,/proc/sys/kernel/hostname、sysctl hostname 和hostname命令应该是功能相同的。

hostname -d 或者 dnsdomainname 命令:

根据/etc/host.conf指定的顺序,先尝试/etc/hosts然后尝试DNS,查找自己的主机名。如果能找到,把第一段去掉之后,输出剩下的部分。这个输出不具备内核意义,只是主机名经过字符串处理之后的派生结果

hostname –fqdn命令:

getaddrinfo()函数查询到的ai_canonname

strace观察到的行为是去DNS查一下主机名(如果主机名是短形式,按需补充/etc/resolv.conf里声明的search/domain后缀)对应的A记录,只要能查到,即使返回的IP地址不是本机的也不管,就以这个主机名作为结果。

hostname –all-fqdn命令:

拿自己的IP地址们循环调用getnameinfo()函数(strace/tcpdump观察到的具体行为是去DNS查PTR记录),并全部输出

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

为什么有些系统是动作式的而非描述式的?

接手一个旧系统,上线发布是“拷贝文件过去,替换掉,重启服务”这样的,而不是“把编译好的东西打包,发上去安装,包的scripts部分带有重启动作”。

是否打包、包里包含什么,很有讲究:

  • 查询当前版本号:XXX -v vs rpm -qi XXX
  • 重启服务:killall XXX && sleep 30 && XXX vs /sbin/service XXX restart
  • 删除软件:rm -fr XXX vs rpm -e XXX
  • 安装依赖: ldd XXX |xargs -n1 yum provides |xargs yum -y install vs rpm -i XXX

……不胜枚举

描述式的好处是可以由机器自动评估效果,而动作式的效果需要人工评估。

描述式的好处的携带的信息(比如依赖关系)更丰富,而动作式的有可能在一开始就没收集齐足够的信息。

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

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
然后增加一个类,带四个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

再把这个注册进去

1
DATABASE_ROUTERS = (DatabaseRWSplitRouter(), )

注意这个注册,可以写字符串形式的dotted_path也可以是一个对象,我偷懒就直接写了一个对象。

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工作有三种做法:

  1. 客户端支持,比如redis cluster客户端需要支持KeySlot计算(我报告的bug: https://github.com/Grokzen/redis-py-cluster/issues/153)
  2. 代理层支持:twemproxy、codis等
  3. 服务器端支持:比如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