在编写单元测试时,每次重新创建MySQL数据库表都花费很长时间,于是在表结果不变的情况下,想使用keepdb参数,即执行
python manage.py test --keepdb, 会报如下错误

1
2
Using existing test database for alias 'default'...
Got an error creating the test database: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CREATE DATABASE IF NOT EXISTS `test_tdcmdb` CHARACTER SET utf8mb4;\n ' at line 2")

查找错误来源,在django.db.backends.mysql.creation.py里的_execute_create_test_db函数中,

1
2
3
4
5
cursor.execute('''
SET @_tmp_sql_notes := @@sql_notes, sql_notes = 0;
CREATE DATABASE IF NOT EXISTS %(dbname)s %(suffix)s;
SET sql_notes = @_tmp_sql_notes;
''' % parameters)

改成

1
2
3
cursor.execute('''
CREATE DATABASE IF NOT EXISTS %(dbname)s %(suffix)s;
''' % parameters)

后,问题得到解决。

最近公司开始使用Gerrit做code review工具,于是才知道git pull还有rebase这个参数。

当本地分支commit后,然后git pull拉取远程分支的提交,之后使用git review生成一个review时,会提示有多个提交。于是想到git rebase, 但是没想到的是git pull也有rebase参数。

洁癖者用 Git:pull –rebase 和 merge –no-ff里提到了pull –rebase的妙用,解决了多年的困惑。之前使用git pull时,老是生成很多无效的merge, 没想到使用git pull –rebase可以解决这个问题

Celery定时任务里介绍,配置定时任务使用crontab, 并设置timezone。于是配置了timezone=”Asia/Shanghai”, 然而时间总是偏差8个小时,在Cannot use local time for cron schedules and UTC for solar schedules里找到解决的办法。原来问题的根源是Celery里的now函数定义出错了。查看now函数的实现

1
2
3
4
def now(self):
"""Return the current time and date as a datetime."""
from datetime import datetime
return datetime.utcnow().replace(tzinfo=self.timezone)

如果写成

1
2
3
4
def now(self):
"""Return the current time and date as a datetime."""
from datetime import datetime
return datetime.now(self.timezone)

就没问题了。
查看datetime.now的实现, 当tz不为空时, datetime.now(tz) 等于tz.fromutc(datetime.utcnow().replace(tzinfo=tz))。所以这是Celery的Bug

但是不能去修改Celery的源码,于是使用评论中提到的解决办法

1
2
3
4
5
6
7
class MyCelery(Celery)
def now(self):
"""Return the current time and date as a datetime."""
from datetime import datetime
return datetime.now(self.timezone)

app = MyCelery()

自从买了1Password后,减轻了记密码的负担,整个人轻松了不少。

在浏览器上输密码时,可以使用Option + Command + \来弹出密码选项,之后搜索需要的密码,敲回车就可以登录。

但在item这种终端中,一直不知道如何输密码。看同事用了一次,才知道诀窍。在终端中,也是使用Option + Command + \来弹出密码选项,之后搜索需要的密码,然后用Shift + Command + C来复制密码,用Command + V来粘贴密码登录。

之前线上跑的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