criu 编译安装
由于yum源中的criu版本是1.6.1 ,而最新版已经是2.x了,所以,学习一下编译安装
编译很简单,主要是依赖: https://criu.org/Installation#Dependencies
- protobuf-c-compiler
libnet-devel
protobuf-python
protobuf-devel
- protobuf-c-devel
- libcap-devel
- libnl3-devel
- libaio-devel
yum 安装:
1 |
yum install protobuf protobuf-c protobuf-c-devel protobuf-compiler protobuf-devel protobuf-python libnl3-devel libcap-devel libaio-devel |
使用过程中,可能用到的python类库:
1 2 3 4 5 |
pip install --upgrade pip pip install pyyaml pip install ipaddr |
GoldenPassport/README.md at master · stanzhai/GoldenPassport
https://github.com/stanzhai/GoldenPassport/blob/master/README.md
google authenticator for mac
一个不错的小程序
https://github.com/yongboy/bindp
Binding specific IP and Port fro Linux Running Application
PHP 中执行命令的几种方式比较
system/exec 函数:
这两个函数会利用/bin/sh 指定的shell来执行指定的命令,所以,指定的命令实际上不仅可以包含可以执行的命令和参数,还可以是对应shell的内置命令以及shell支持的控制结构。
缺点: 无法区分标准输出与标准错误
注意: 这两个函数执行的外部命令默认继承了php进程的标准输入、标准输出和标准错误,你可以通过返回值得到进程的标准输出,但是标准错误却悄悄地流进了php进程定义的标准错误输出里面去了;更加隐晦的是,对于php-fpm进程来讲,该外部进程也默认listen(继承来的)了php-fpm监听的端口,但是肯定不会去处理php-fpm请求,一般情况下不会有问题,特殊情况下回让你莫名其妙
pcntl_exec 函数:
该函数会取代当前执行的进程空间,而且是不会再回到当前进程空间,可能你很少会用到该函数,但是,当你确实需要该函数的时候,却又是没有其他函数可以替代的,如:
1 |
# php -d error_log="" -r 'pcntl_exec("/bin/sleep", array(1));echo 2;' |
echo 2 是不可能被执行到的
注意: pcntl_exec不同于其他函数,并不会使用shell来执行指定的命令,而且指定的命令需要是要执行的可执行文件的全路径
proc_open 函数:
这个是我最喜欢的函数:
- 可以通过proc_close得到外部进程的返回值
- 可以定义外部进程的标准输入、标准输出、标准错误,并且可以区分外部进程的标准输出和标准错误
- 一定程度上还可以实现并行执行多个外部命令
proc_open 示例:
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 27 28 29 30 31 32 |
<?php $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a file to write to ); $cwd = '/tmp'; $env = array('some_option' => 'aeiou'); $process = proc_open('ssh -i /home/phpor/.ssh/id_rsa phpor@localhost ', $descriptorspec, $pipes, $cwd, $env); if (is_resource($process)) { // $pipes now looks like this: // 0 => writeable handle connected to child stdin // 1 => readable handle connected to child stdout // Any error output will be appended to /tmp/error-output.txt fwrite($pipes[0], $argv[1]); fclose($pipes[0]); echo stream_get_contents($pipes[1]); fclose($pipes[1]); echo stream_get_contents($pipes[2]); fclose($pipes[2]); // It is important that you close any pipes before calling // proc_close in order to avoid a deadlock $return_value = proc_close($process); echo "command returned $return_value\n"; } |
Linux之进程会计
什么是进程会计?
进程会计记录了什么时间启动了什么进程,进程的属主是什么,进程的结束时间等信息。
linux默认并没有开启进程会计,那么如何开启进程会计呢?
centos中提供了一个叫做psacct的软件包,其中包含accton 、lastcomm、ac、sa、dump-acct、dump-utmp几个命令,可以帮助我们开启、关闭、查看进程会计信息,简要介绍如下:
accton on|off|file
打开、关闭进程会计,也可以直接accton file来指定进程会计信息的记录位置,同时开启进程会计;注意:
- 进程会计信息的默认存储位置: /var/account/pacct
- 进程会计信息存储文件需要预先手动创建(touch一个就行)
实现原理:
linux提供了一个acct系统调用,用来通知内核信息进程会计:
accton on 等同于: acct(“/var/account/pacct”)
accton off等同于: acct(NULL)
(注意:有的版本可能不能accton off,而是不带任何参数就意味着off)
lastcomm: 查看进程会计信息
1 2 3 4 5 6 7 8 |
# lastcomm -f acct accton root pts/0 0.00 secs Tue Apr 4 13:13 sh zabbix __ 0.00 secs Tue Apr 4 13:13 wc zabbix __ 0.00 secs Tue Apr 4 13:13 grep zabbix __ 0.00 secs Tue Apr 4 13:13 netstat zabbix __ 0.01 secs Tue Apr 4 13:13 accton root pts/0 0.00 secs Tue Apr 4 13:13 ... |
sa:统计进程会计信息
1 2 3 4 5 6 7 8 9 10 |
# sa -a acct 227 0.02re 0.00cp 0avio 28444k 2 0.00re 0.00cp 0avio 29776k netstat 88 0.00re 0.00cp 0avio 31031k ls 84 0.01re 0.00cp 0avio 26976k sleep 10 0.00re 0.00cp 0avio 28816k sh 10 0.00re 0.00cp 0avio 28160k grep 8 0.00re 0.00cp 0avio 28368k awk 8 0.00re 0.00cp 0avio 26976k head 8 0.00re 0.00cp 0avio 26976k cat |
dump-utmp
1 2 3 4 5 |
dump-utmp /var/log/wtmp|head reboot |~ |2|~~ | 0|3.10.0-327.el7.x86_64 |Tue Mar 7 00:42:20 2017 |tty1 |5|tty1| 1806| |Mon Mar 6 16:42:36 2017 LOGIN |tty1 |6|tty1| 1806| |Mon Mar 6 16:42:36 2017 runlevel |~ |1|~~ | 51|3.10.0-327.el7.x86_64 |
理解Kubernetes网络之Flannel网络 | Tony Bai
php-fpm 与 so_reuseport
问题:
如何让php-fpm能优雅地重启?
办法:
- 修改php-fpm listen的方式,开启so_reuseport
- 重启php-fpm时,先启动新进程,再停止旧进程
问题:
- 新进程和旧进程有不同的tcp backlog队列
- 系统会将新建立的连接平均分配到每个backlog队列中,新旧进程只能消费自己的backlog队列
- 新进程的所有子进程共享一个新的backlog队列,就进程的所有子进程共享一个旧的backlog队列
- 旧进程如何才能将backlog中的连接消费完?
- 旧进程如何阻止系统不要再分配连接到自己的backlog队列?
- 旧进程如何判断自己的backlog队列已空?
- 如果php-fpm进程通过exec、system、popen、proc_open等方式执行了外部的程序,则外部程序会继承所有的文件描述符(如果不做有意或无意的特殊处理的话,也会包括listen 9000 的文件描述符);问题在于,这个外部的程序虽然listen了9000端口,但是它绝对不会帮你处理9000端口的请求的,然后,系统分配过来的请求就会被阻塞
kill -SIGQUIT 可以解除子进程对9000端口的listen,却不能解除master进程对9000端口的listen
子进程的listen是继承父进程的
子进程接到SIGQUIT 信号后,close掉listen的文件描述符,然后创建了一个新的socket,如下:
(为什么要创建一个新的socket?)
同一个文件描述符被fork到多个子进程后,每个进程的关闭只会在本进程内关闭(让该资源和本进程脱离关系),然后减少该资源的引用计数,直到最后一个引用被解除,该资源才真正销毁
关于上述的问题7:
- 无意中对listen 9000的覆盖
- 当使用popen(“sleep” , “w”)的方式,就是以“写”的方式打开外部程序,则外部程序继承过来的标准输入0(原本listen 9000的文件描述符)会被覆盖
- 这时候就不会出现问题。 不过,你很少会如此打开一个外部程序的,常见的方式是以“读”的方式打开外部程序的,这时候,外部程序要覆盖掉的是文件描述符1,而0不会受影响
- 能否刻意覆盖掉文件描述符0呢? 比如: popen(“sleep”, “rw”) 读写方式打开?答案是:不可以; popen只允许单向打开(原因未深究,参考文档: http://php.net/manual/en/function.popen.php )
- 那么通过proc_open 实现呢?答案是: 可以; 如下:
123456789<?php$descriptorspec = array(0 => array("pipe", "r"), // stdin is a pipe that the child will read from1 => array("pipe", "w"), // stdout is a pipe that the child will write to2 => array("file", "/tmp/error-output.txt", "a") // stderr is a file to write to);$process = proc_open('sleep 10000',$descriptorspec, $pipe);print_r($pipe);sleep(10000);
则sleep进程的文件描述符如下:
这样就对了; 而且,proc_close() 可以获取外部进程的退出码
- 如果觉得proc_open 用起来比较麻烦,那么,或许可以给启动的外部程序来一个自己的shell,该shell负责关闭文件描述符0, 如: a.sh
123#!/bin/bashexec 0<&-$@
注意: 这个shell会存在一些问题,如下:
改进如下:12345#!/bin/bashexec 0<&-cmd=$1shift$cmd "$@"
到现在为止,我们有能力不让外部的子进程来影响fpm请求的处理了;但是,还有一个listen 9000 但是不能处理请求的进程,那就是: php-fpm master进程
我们的处理方法可以如下:
- kill -SIGQUIT pid-of-php-fpm-master && sleep 1 && kill -9 pid-of-php-fpm-master (里面有 -9 ,好不和谐)
最后:
我们还没有解决如何让旧进程消费完旧进程的backlog中剩余的连接的问题,稍后研究。
分析:
socket中有一个shutdown的方法,可以关闭socket的读或(和)写,会触发关闭已建立的连接的方法,(对于listen的socket没有对端,想必不能用shutdown方法),测试发现:
- shutdown可以停止socket的listen,而不关闭文件描述符,确实可以实现关闭进程的listen,但是同时也会丢弃backlog中的连接,不管shutdown的是读还是写
- 测试脚本:
12345<?php$s = socket_create_listen (8882, 1);fgets(STDIN);socket_shutdown($s);fgets(STDIN);
(如果能通过单向关闭的方式通知内核不要再往该backlog分配连接不是很好吗?)
一种其它的实现方式:
在so_reuseport出现之前(包括现在)有一些程序是可以实现优雅重启的,原理就是,新旧进程时间继承lisen的文件描述符; 其实,继承文件描述符需要不小的工作量的。
如果能有一种标准的接口来处理继承文件描述符的事情,那么完全可以开发一个和应用无关的小程序,用来创建socket端口,然后遗传给真正要干活的应用程序,只需要遗传给应用程序的master进程就算OK了,如果需要重启程序,则只需要fork新的应用程序的master进程,然后发信号让旧的空闲的应用进程退出就可以了,这样就不会丢弃已创建的连接。
参考:
https://my.oschina.net/miffa/blog/390931