之前线上跑的Celery出现了堵塞,导致任务无法执行。这次抽空找找原因。

堵塞的时候,从flower上看到很多任务都处于started状态,而started状态的任务数量正好等于celery进程数。

同样提交大量上次执行的任务,Celery又堵塞了。查看执行的任务,没发现有什么特别的,唯一的问题是任务里再套了一个Celery异步任务,并需要获取到执行的结果。

仔细分析,问题就出在这里。当提交大量的这种任务时,把Celery进程都占满了,然而这些任务都需要另起一个Celery任务才能执行完,此时已经没有Celery进程可以使用,于是所有的任务都无法执行。

解决的办法是,Celery任务里不要再起Celery异步任务, 如果非要这样做,避免大量出现这样的任务。

后来,同事的Celery也出现了堵塞,与他一起查找,没发现问题。于是请教另一位同事,他用pstree命令和strace命令把问题的原因找到。

联系作者

在用Vue写前端时,看到同事写的代码里

1
2
this.subnetIps[String(subnet_id)] = ''
this.subnetIps[String(subnet_id)] = ips

觉得很奇怪,为什么要先置空,再赋值?尝试把this.subnetIps[String(subnet_id)] = ''去掉后,发现前端的值不会发生变化。于是看Vue文档,在检测变化的注意事项里提到这是由于Javascript语言的缺陷造成的。文档里提到可以使用$set方法,于是把两条语句改成this.$set(this.subnetIps, String(subnet_id), ips)

联系作者

最近线上生产环境出了一次故障,大量机器出现大量僵死进程,导致服务僵死。部门里组织同事小黑屋查找原因,找了大半天终于找到原因,罪魁祸首竟然是安全部门的漏洞扫描。

刚开始的时候,发现机器上有大量Salt minion进程,以为是Salt的原因。后来以为同事发现这些僵尸进程的父进程都是init进程,而init进程处于T(stopped)状态,所以这些僵尸进程都没法回收,导致大量僵尸进程堆积。同事还推荐使用kill -SIGCONT pid恢复进程状态。

接下来就是要找到使init变成T状态的根源,经过排查,终于锁定使安全部门的漏洞扫描。按常理,正常的扫描应该是可以反复执行,不会对系统造成干扰。但安全部门新增加的扫描插件却会发送stop信号给init进程,而在CentOS 6.6上,init进程响应了这个信号,于是进入stopped状态。于是init无法回收僵尸进程,导致僵尸进程越来越多。

联系作者

需要通过带外卡获取服务器的业务网卡,本来想使用 IPMI 获取的,但惠普的服务器不支持。好在可以通过 SSH 的方式获取,SSH 登录后执行 show /system1/network1/Integrated_Nics 命令即可。

但是不知道什么原因,使用 Python 的 paramiko 模块无法登录带外的 SSH, 于是只好用其它办法,同事建议用 Expect 来实现自动登录, 于是编写了如下脚本,替换上相应的主机,用户,密码即可

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/expect -f
set ip 10.48.2.3
set password test123
set timeout 5
spawn ssh administrator@$ip
expect {
"*yes/no" { send "yes\r"; exp_continue}
"*password:" { send "$password\r" }
}
expect "*hpiLO*"
send "show /system1/network1/Integrated_Nics\r"
send "exit\r"
expect eof

参考资料

联系作者

之前服务是用 Tornado 启动,但响应速度很慢,有时候会被一些任务给卡死,于是一直想提高性能,后来发现是没有使用多进程。看到 tornado.httpserver — Non-blocking HTTP server 中的配置,于是加上多进程。

1
2
3
4
5
sockets = tornado.netutil.bind_sockets(8888)
tornado.process.fork_processes(4)
server = HTTPServer(app, xheaders=True)
server.add_sockets(sockets)
IOLoop.current().start()

但是在 debug 模式下,上面的配置是会报错的,所以 debug 模式下还是使用单进程配置。

1
2
3
server = HTTPServer(app)
server.listen(8888, xheaders=True)
IOLoop.current().start()

现在响应速度有了质的飞跃。

至于 xheaders 配置,是为了获取用户的真实 IP。

联系作者

随着机器数量增多,单个 Salt master 已经无法满足性能需求,于是需要采用多 master, 多 syndic 结构,于是需要升级 Salt Minion 。升级过程中出现了一些问题。

在升级 Salt inion 的时候,需要关闭当前的 Salt minion,发现以前通过 Salt 启动的一些进程,如 redis 等等,也跟着关闭了,这是个问题。

查找之后,在 Salt 上也提到这个问题,salt-minion restart causes all spawned daemons to die on centos7 (systemd),主要原因是 systemd 里, 杀死进程的默认方式是 control-group ,也就是同一个组下主进程被杀死,其它进程都会被杀死。而通过 Salt 启动的那些服务与 Salt 是同一个组,Salt 是主进程。

解决办法是更改 salt-minion.service 的 KillMode 为process

联系作者

项目里有一个脚本列表接口响应非常慢,需要10秒以上,看程序,认为是访问数据库过多,优化之后还是很慢。继续看代码,没看出什么名堂。同事推荐使用 Profile 看看,于是使用,解决了问题。

根据 Profiling a Werkzeug (flask) app 这篇文章,在代码里加上

1
2
3
4
from werkzeug.contrib.profiler import ProfilerMiddleware
from myapp import app # This is your Flask app
app.wsgi_app = ProfilerMiddleware(app.wsgi_app)
app.run(debug=True) # Standard run call

再次调用接口时,终端上就显示了一些调用信息,根据这些信息去代码里找原因,解决问题。

最后发现是接口里返回的最后修改时间和修改原因都是从 git 仓库里访问,脚本多的话,就很慢了。将这两个信息保存在数据库中就可以解决问题。

联系作者

使用salt-minion时,会遇到文件数不够的问题。一个常见的误区是认为修改/etc/security/limits.conf即可,后来发现这是不对的。man pam_limits就会发现/etc/security/limits.conf主要作用于user-session的情况,如sshd, login, su等等。而对于使用sysvinit和systemd启动的服务,/etc/security/limits.conf是不起作用的,此时可以在相应服务的启动配置里修改文件数配置。

对于salt-minion文件数的修改需要根据系统版本而定。

CentOS 7

修改/usr/lib/systemd/system/salt-minion.service文件,修改其中的LimitNOFILE配置
之后重启 salt-minion

  • systemctl daemon-reload
  • systemctl restart salt-minion.service

CentOS 6

修改/etc/init.d/salt-minion启动脚本
添加文件数ulimit配置。例如设置最大100000,可以在启动脚本前面添加ulimit -HSn 100000,之后重启 salt-minion

  • service salt-minion restart

参考资料:

联系作者

Pylint

Pylint 用于 Python 静态代码检查。默认代码风格遵循 PEP08

安装

执行 pip install pylint 即可

使用配置文件

配置文件可以通过如下命令生成pylint --generate-rcfile > .pylintrc。执行 pylint 时,可以通过指定 –rcfile 参数来加载配置文件。而默认配置文件加载顺序可以参考命令行参数这节。例如,对当前目录下所有 Python 文件作代码检查,执行 pylint --rcfile .pylintrc *.py 即可

Flask代码检查

对于Flask, 有pylint-flask 这个 Pylint 插件用来代码检查。pip install pylint-flask安装后,添加 –load-plugins 参数即可启用,如pylint --load-plugins pylint_flask

警告忽略

有时 Pylint 的检查不满足需求,太繁琐,此时可以忽略它。如在for d in data:里,会报Invalid variable错误,即, 此时加上# pylint: disable=invalid-name可以忽略这个警告。暴力一点的方法是在文件开头添加# pylint: disable=invalid-name,这样会对整个文件忽略检查。更暴力的方法是修改 .pylintrc 文件,在disable这项里添加 invalid-name , 这样就会对所有文件忽略这个检查。

pre-commit

pre-commit 用来配置 commit 代码时检查代码。

安装

执行 pip install pre-commit 即可。

添加移除 git hooks

执行 pre-commit install , 将 pre-commit 添加到 git hooks 中

配置

在项目根目录下,添加.pre-commit-config.yaml文件即可进行配置,如下就是一个配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-   repo: https://github.com/pre-commit/pre-commit-hooks
sha: v0.7.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: autopep8-wrapper
- id: check-docstring-first
- id: check-json
- id: check-added-large-files
- id: check-yaml
- id: debug-statements

- id: requirements-txt-fixer
- repo: https://github.com/pre-commit/pre-commit
sha: v0.11.0
hooks:
- id: validate_config
- id: validate_manifest
- repo: local
hooks:
- id: pylint
name: pylint
entry: pylint
language: system
files: \.py$
exclude: test_gevent.py|app/rpc/notify_manager
args: [--rcfile=.pylintrc, --load-plugins=pylint_flask]

此后,每次提交代码时,都会进行代码规范检查。

移除 git hooks

执行 pre-commit uninstall, 将 pre-commit 从git hooks中移除

联系作者

有些时候,需要通过Zabbix的API添加维护,挂起Zabbix的报警,简单的API调用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from zabbix_client import ZabbixServerProxy
zapi = ZabbixServerProxy("http://127.0.0.1/")
print zapi.user.login(user="test", password="password")
print zapi.host.get(output=['hostid', 'host'])
import time
params = {
"name": "madfsdfsadsdfsdf",
"active_since": int(time.time()),
"active_till": int(time.time()) + 3600,
"hostids": ["10109"],
"timeperiods": [
{
"start_time": 64800,
"period": 3600
}
]
}
# print zapi.maintenance.create(**params)
params = {
"hostids": ["10109"]
}
print zapi.maintenance.get(**params)

params = ('19',)
print zapi.maintenance.delete(*params)

联系作者