Ad Discendum, Non Ad Docendum

Для того, чтобы учиться, а не для того, чтобы учить

PHP FastCGI

Posted by Денис Лозко Sun, 04 Feb 2007 09:33:00 GMT

Наверное, как и много кто другой, я подписан на дайджест сообщений FIDO-группы fido7.ru.unix.bsd, в очередом списке активных тем оказалась тема "ngix + php-fastcgi". Полез я посмотреть. Как оказалось, это продолжение темы "Apache 2.2.4 && mod_php 5.2.0 && freebsd 6.1", где человеку посоветовали "выкинуть апач нафиг, ставить nginx+php как fastCGI".

Не буду косаться темы перехода с Apache на nginx, потому как клоню я не к тому. Прочтя там же сообщение от некого Alex'а Ivanov'а, вспомнил, что у меня у самого не дописан толком скрипт старта PHP как FastCGI-приложения.

Нет. Он, конечно, рабочий, но вот не до конца реализовано в нем все, что задумано и есть некоторые недочеты. Сообственно, вот он, знакомтесь:

/usr/local/etc/rc.d/php_fastcgi:
1 #!/bin/sh
2 #
3
4 # PROVIDE: php_fastcgi
5 # REQUIRE: DAEMON
6 # BEFORE:  LOGIN
7 # KEYWORD: shutdown
8
9 . /etc/rc.subr
10
11 name="php_fastcgi"
12 rcvar=`set_rcvar`
13
14 load_rc_config $name
15
16 : ${php_fastcgi_enable="NO"}
17 : ${php_fastcgi_user="www"}
18 : ${php_fastcgi_bindaddr="127.0.0.1"}
19 : ${php_fastcgi_bindport="9000"}
20 : ${php_fastcgi_children="5"}
21 : ${php_fastcgi_max_requests="1000"}
22
23 export PHP_FCGI_CHILDREN=${php_fastcgi_children}
24 export PHP_FCGI_MAX_REQUESTS=${php_fastcgi_max_requests}
25
26 command="/usr/local/bin/php-cgi"
27 command_args="-q -b ${php_fastcgi_bindaddr}:${php_fastcgi_bindport} &"
28
29 load_rc_config $name
30
31 run_rc_command "$1"

По сути, сам скрипт - вариация на тему FreeBSD rc-скрипта. Идея не моя, а честно подсмотрена у Alexey'я N. Kovyrin'а из его поста о настройке связки nginx+php, а я лиш перенес его в rc.d, понавесив сверху правельных рюшечек из rc.subr(8) и добавив возможность его конфигурации по-пацански из /etc/rc.conf.d/php_fastcgi.

Так вот, собственно о том, что меня в нем не устраивает.

То что он матерится при стопе:
# /usr/local/etc/rc.d/php_fastcgi stop
Stopping php_fastcgi.
kill: 56598: No such process
kill: 56599: No such process
kill: 56600: No such process
kill: 56601: No such process
kill: 56602: No such process

И то, что в нем отсутвует ограничение на переменные среды в которой запускаться сам php-cgi.

В принципе - обе проблемы имеют весьма очерченую суть и известное место, "откуда ростут ноги".

По поводу остановки процессов.

Как говорится в документации к rc.subr(8), если run_rc_command используется с параметром stop, то $sig_stop посылается всем процессам. И, судя по нутрям самого /etc/rc.subr, не просто killall(1), а каждому процессу по отдельности. А тут же имеем случай, когда родительский процесс при завершении завершает сначала дочерние, что и выливается в фактической отсутвие порцесса с pid из списка, которые необходимо застопить. При этом в самом rc-скрипте можно сказазать, что работать с pid'ом из $pidfile. Проблема в том, что некому этот пид-файл создать:

> php-cgi -h
Usage: php [-q] [-h] [-s] [-v] [-i] [-f <file>]
       php <file> [args...]
  -a               Run interactively
  -b <address:port>|<port> Bind Path for external FASTCGI Server mode
  -C               Do not chdir to the script's directory
  -c <path>|<file>  Look for php.ini file in this directory
  -n               No php.ini file will be used
  -d foo[=bar]      Define INI entry foo with value 'bar'
  -e               Generate extended information for debugger/profiler
  -f <file>         Parse <file>.  Implies `-q'
  -h               This help
  -i               PHP information
  -l               Syntax check only (lint)
  -m               Show compiled in modules
  -q               Quiet-mode.  Suppress HTTP Header output.
  -s               Display colour syntax highlighted source.
  -v               Version number
  -w               Display source with stripped comments and whitespace.
  -z <file>         Load Zend extension <file>.

Полез я по этому поводу в исходнки самого PHP (если интересно 5.2.0) -- авось есть какая недокументровная фича по поводу пидфайла для fcgi-режима. Особо не надеясь, и не растроившись ничего не найдя, отдуши посмеялся:

> grep -ri pid php-5.2.0/Zend/
php-5.2.0/Zend/zend_config.nw.h:#define HAVE_GETPID 1
php-5.2.0/Zend/Zend.m4:AC_CHECK_FUNCS(memcpy strdup getpid kill strtod strtol finite fpclass)
php-5.2.0/Zend/zend_alloc.c:#if ZEND_DEBUG && defined(HAVE_KILL) && defined(HAVE_GETPID)
php-5.2.0/Zend/zend_alloc.c: kill(getpid(), SIGSEGV);
php-5.2.0/Zend/ChangeLog:      Fix another stupid mistake.
php-5.2.0/Zend/ChangeLog:      Fix stupid mistake that only affected interactive mode.
php-5.2.0/Zend/ChangeLog:    * zend_list.c: Fix a stupid bug (from stefan@roehri.ch)
php-5.2.0/Zend/ChangeLog:    * zend-parser.y: I'm on a roll.  Fix a nasty yet stupid AiCount bug
php-5.2.0/Zend/ChangeLog:    * Fix a stupid bug - forgot to pass CLS_C to some compiler function.  For some reason

Вероятность встретить в исходниках PHP слово pid в составе корня слова stupid предшествующему словам mistake или bug выше, нежели обособленно само слово (5 против 4).

Ну что ж -- будем своими силами решать. Костылем.

28
29 pidfile="/var/run/${name}.pid"
30 start_postcmd="ps -U ${php_fastcgi_user} -o 'pid,command'|grep ${command}|head -1|awk -- '{print \$1}' > ${pidfile}"
31

Т. е. мы сами создаем пидфайл и пишем в него процесс, который ps(1) покажет выше всех, тот у которого меньший pid (у меня по крайней мере так и было). Разумно, предположить, что это и есть родитель.

Один минус - если удалить пидфайл вручную rc.subr(1) будет считать, что процесс уже остановлен, со всеми вытекающимипоследствиями. Но это скорей не проблемы скрипта, а проблемы рук, которые так сделают — ими управляет тупая голова.

Окружение

А ничего мудрить нового не будем, и воспользуемся все тем же старым-добрым env(1), так же как и в оригинальном скрипте у Alexey'я N. Kovyrin'а, с небольшими доработками:

31
32 : ${php_fastcgi_allowed_env=""}
33 _allowed_env="ORACLE_HOME PATH USER PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS"
34 _allowed_env="${_allowed_env} ${php_fastcgi_allowed_env}"
35 start_precmd="${name}_start_precmd"
36 php_fastcgi_start_precmd()
37 {
38     export USER=${php_fastcgi_user}
39     E=
40     for i in ${_allowed_env}; do
41         eval _val="\$$i"
42         if [ "${_val}_x" != "_x" ]; then
43             eval _add="$i=$_val"
44             E="${E} ${_add}"
45         fi
46     done
47     command="env - ${E} ${command}"
48 }
49
Теперь проверим окружение PHP:
Environment
VariableValue
PATH /home/talmuth/personal/bin:/home/talmuth/personal/lib/ruby/gems/bin:/home/talmuth/bin:/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/games
USER www
PHP_FCGI_CHILDREN 5
PHP_FCGI_MAX_REQUESTS 1000

как видим - есть нам вселенское счастье.

Разложим все по своим местам

В результате всех самоистязаний получаем простой и рабочий скриптик:

1 #!/bin/sh
2 #
3
4 # PROVIDE: php_fastcgi
5 # REQUIRE: DAEMON
6 # BEFORE: LOGIN
7 # KEYWORD: shutdown
8
9 . /etc/rc.subr
10
11 name="php_fastcgi"
12 rcvar=`set_rcvar`
13
14 load_rc_config $name
15
16 : ${php_fastcgi_enable="NO"}
17 : ${php_fastcgi_user="www"}
18 : ${php_fastcgi_bindaddr="127.0.0.1"}
19 : ${php_fastcgi_bindport="9000"}
20 : ${php_fastcgi_children="5"}
21 : ${php_fastcgi_max_requests="1000"}
22 : ${php_fastcgi_allowed_env=""}
23
24 export PHP_FCGI_CHILDREN=${php_fastcgi_children}
25 export PHP_FCGI_MAX_REQUESTS=${php_fastcgi_max_requests}
26
27 command="/usr/local/bin/php-cgi"
28 command_args="-q -b ${php_fastcgi_bindaddr}:${php_fastcgi_bindport} &"
29 pidfile="/var/run/${name}.pid"
30
31 _allowed_env="ORACLE_HOME PATH USER PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS"
32 _allowed_env="${_allowed_env} ${php_fastcgi_allowed_env}"
33
34 start_precmd="${name}_start_precmd"
35 start_postcmd="ps -U ${php_fastcgi_user} -o 'pid,command'|grep ${command}|head -1|awk -- '{print \$1}' > ${pidfile}"
36
37 php_fastcgi_start_precmd()
38 {
39   export USER=${php_fastcgi_user}
40   E=
41   for i in ${_allowed_env}; do
42     eval _val="\$$i"
43     if [ "${_val}_x" != "_x" ]; then
44       eval _add="$i=$_val"
45       E="${E} ${_add}"
46     fi
47   done
48   command="env - ${E} ${command}"
49 }
50
51 load_rc_config $name
52
53 run_rc_command "$1"

Который в text/plain можно скачать отсюда

Have fun!!!

Posted in , , | 6 comments | Tags , , | atom

Trackbacks

Use the following link to trackback from your own site:
http://blog.org.ua/trackbacks?article_id=php-fastcgi&day=04&month=02&year=2007

Comments

Leave a response

  1. Avatar
    pahan
    8 days later:

    Превед ) Апач - хорошо, но не на отдаче большого кол-ва статики и на огромном трафе ) А вот php-cgi надо будет с нгинксом затестить…

  2. Avatar
    pahan
    26 days later:

    Это, сцука, пиздец…

  3. Avatar
    kmihas
    about 1 year later:

    спасибо скрипт красиво работает, есть только одно но .. при старте не пишет PID процесса в pid-файл .. посмотрел права на файл –rx-r–r– root wheel почему так и не понял. не подскажете что не так делаю?

  4. Avatar
    Денис Лозко
    about 1 year later:

    посмотрел права на файл –rx-r–r– root wheel почему так и не понял

    start_postcmd="ps -U ${php_fastcgi_user} -o 'pid,command'|grep ${command}|head -1|awk -- '{print \$1}' > ${pidfile}"
    

    Эта строчка создаёт PID-файл от имени пользователя запустившего сам скрипт.

    при старте не пишет PID процесса в pid-файл

    Попробуйте после запуска скрипта запустить эту же комманду.

  5. Avatar
    kmihas
    about 1 year later:

    2 Денис Лозко вручную то команда выполняется … но ведь при старте то он тоже от рута выполняется .. получается что я запустил процесс этим скриптом, он врубился от nobody, а вырубить его этим скриптом я уже не могу … не красиво как-то, вот и хочется разобраться где я не прав

  6. Avatar
    Денис Лозко
    about 1 year later:

    Можно попробовать вместо банального

    #!/bin/sh
    

    дописать

    #!/bin/sh -x
    

    Оно тогда много чего будет в стдаут выкидывать. Можете этот вывод скопировать куда нить в пасти, посмотрю, вдруг чего и увижу.

Leave a comment