09.01.2012

Какой файл .php исполняется как FastCGI на моем сервере и грузит CPU?

Ответа нет, потому как вопрос задан некорректно. Чтобы докапаться до истины расскажу в кратце как работает FastCGI.
От клиента, будь это браузер или реверс-прокси перед вэб-сервером, поступает запрос на исполнение некоего .php файла (пусть в нашем примере будет index.php). Вэб-сервер создает отдельный от себя процесс, который видно в топе как php-cgi, и говорит ему: "Ты создан! Теперь вот тебе файл index.php, исполни его и верни мне результат." Далее происходит вот что. FastCGI процесс берет файл index.php и проходится по его коду (вопрос компиляции я тут опущу за ненадобностью). На пути встречает include соседних файлов и их соответственно тоже запускает. И, напомню, все это один процесс php-cgi, который пробегает по многим .php файлам.
Затем, как только php-cgi процесс отдаст результаты обратно вэб-серверу, вэб-сервер, исходя из конфига, решит что делать с этим процессом - либо убить, либо оставить для обработки следующих запросов (чтобы заново его не рожать). Но, опять же согласно конфигу, он будет еще поглядывать за этим процессом: сколько раз он выполнял задачи, как часто проставивает, не превратился ли в зомби, не завис ли, не обработал ли слишком много запросов и так далее, пока все-таки не найдет достаточного основания для убийства этого процесса и порождения нового на его место.
То есть один php-cgi процесс - это не только не один .php файл, это еще и не одна порция таких файлов.Чтобы было более понятно, процесс php-cgi, который мы видим в ps, это ничто иное как контейнер, запущенный в памяти интерпретатор, наподобии как Tomcat у Java.
Именно поэтому не нужно удивляться, что при ограничении max_execution_time = 120 секунд в php.ini, в списке процессов вдруг виден процесс, который висит вот уже неделю. Он таких по 120 секунд за свою недельную жизнь видал не перевидал, все через него прошли, как через матрешку.


Для наглядности проведем эксперимент.

Посмотрим, сколько процессов у нас на сервере запустил некто user1316 (много строк я потер, чтобы не получалась длинная простыня).

node1-2:~# ps aux | grep user1316 | grep -v grep

user1316 48214  5.1  0.1 234148 33148 ?        S    10:54   1:52 /usr/bin/php5-cgi php
user1316 48215  5.0  0.1 249040 48068 ?        S    10:54   1:50 /usr/bin/php5-cgi php
user1316 48238  5.9  0.1 244976 43980 ?        S    10:54   2:11 /usr/bin/php5-cgi php
user1316 48242  3.7  0.2 251160 49780 ?        S    10:54   1:22 /usr/bin/php5-cgi php
user1316 48367  5.4  0.1 242904 43752 ?        S    10:54   1:58 /usr/bin/php5-cgi php
user1316 48383  5.3  0.1 244968 43856 ?        S    10:54   1:57 /usr/bin/php5-cgi php
user1316 48388  5.7  0.1 243432 42452 ?        S    10:54   2:06 /usr/bin/php5-cgi php
user1316 48399  4.4  0.1 244968 43968 ?        S    10:54   1:37 /usr/bin/php5-cgi php
user1316 51261  5.6  0.1 243436 42456 ?        S    11:01   1:37 /usr/bin/php5-cgi php
user1316 51262  5.8  0.1 244712 43736 ?        S    11:01   1:41 /usr/bin/php5-cgi php
user1316 52196  8.6  0.1 234540 33568 ?        S    11:04   2:16 /usr/bin/php5-cgi php
user1316 58913  2.9  0.1 244928 43772 ?        S    11:22   0:14 /usr/bin/php5-cgi php
user1316 59862  5.3  0.1 246504 47040 ?        R    11:25   0:18 /usr/bin/php5-cgi php
user1316 59863  4.4  0.1 242908 43448 ?        S    11:25   0:15 /usr/bin/php5-cgi php
user1316 61984  1.6  0.1 248832 49344 ?        S    11:30   0:00 /usr/bin/php5-cgi php
user1316 62000  1.1  0.1 242684 43180 ?        S    11:30   0:00 /usr/bin/php5-cgi php
user1316 62001  4.3  0.1 247412 47692 ?        R    11:30   0:01 /usr/bin/php5-cgi php
user1316 62006  3.1  0.1 231848 32376 ?        S    11:30   0:01 /usr/bin/php5-cgi php
user1316 62323  9.2  0.1 248028 48408 ?        R    11:30   0:01 /usr/bin/php5-cgi php
user1316 62324  2.3  0.1 242688 43188 ?        S    11:30   0:00 /usr/bin/php5-cgi php

Обратите внимание, есть процессы S (sleeping), а есть процессы R (running).
Sleeping - это процесс, который уже обработал основной .php, выданный ему вэб-сервером и все его инклуды. Теперь он просто висит в памяти и спит, пока его не пнёт вэб-сервер с новым заданием.
Running - это процесс, который в данный момент действительно что-то делает для вэб-сервера, а как сделает, то уснёт (S), если вэб-сервер его не убьет.

Теперь попробуем подцепиться к спящему процессу (бегущие (R) процессы нам неинтересны - пока мы будем писать команду, они уже умрут). Возьмем самый последний в списке. Его PID - вторая колонка:

strace -p 62324
Process 62324 attached - interrupt to quit
accept(0,

и все... больше ничего не говорит нам... потому что он спит. Но мы подождем! В течении нескольких минут, если нам повезет, мы будем наблюдать феерический объем системных вызовов, часть из которых будет содержать вызываемые файлы.

Но если все же вопрос "так какие файлы то?" остался неотвеченным, то вот, что даст ответ:
strace -e open,access -p 62324
А вот так можно померять, на что сколько времени уходит у данного процесса. Эта команда ничего не покажет, ее нужно прервать на ^C через произвольный промежуток времени. Но при выходе стрейс выплюнет красивую табличку с отчетом:
strace -c -p 62324
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 66.04    0.010125           0     20990      1314 open
 11.51    0.001765           0     75052         6 read
  3.59    0.000551           0     19012           munmap
  3.36    0.000515           0     28905           fstat
  2.44    0.000374           0     19012           mmap
  2.37    0.000363           0     19848           close
  2.17    0.000332           0      4536           write
  2.16    0.000331           0     29262           lseek
  1.72    0.000264           0     14543      1385 lstat
  1.21    0.000185           0      3301           poll
...
    <вырезано еще около 20-ти системных вызовов
      но они занимают менее 1% времени каждый>
...


Что-то интересное на эту тему есть в статье Оптимизируем загрузку PHP-кода в 22 раза, или почему FastCGI не ускоряет PHP



03.01.2012

Аутентификация SSH через LDAP

Для полного порядка в корпоративной сети нужно, чтобы все сервисы аутентифицировались через одно место :) Каждый, как говорится, понял в меру своей испорченности.

Делается это следующим образом. Как многие знают, сегодня считается моветоном делать самостоятельные проверки паролей, групп и прочего. Давным давно уже изобрели единый центр проверок PAM. Даже утилита login, которая запускается, когда Вы коннектитесь к консоли, проверяет не сама, а отдает эту задачу "на аутсурс" PAM'у. Соответственно и OpenSSH сам не проверяет пароли.
Изначально у нас есть PAM-модуль для проверки через passwd (вообще их намного больше). Мы же поставим еще один PAM-модуль: для проверки через LDAP. А еще мы дополним NSS-переключатель новой опцией, чтобы он говорил всем, что можно проверять не только через passwd, но и через LDAP.

Сложность аутентификации SSH состоит в том, что запроcы будут идти к объектам PosixAccount, а в нормальном LDAP таким делать нечего. Кроме того, желательно также, чтобы в LDAP проверялись не пароли, а публичная часть ключа.

Итак сначала сделаем, чтобы проверялись хотя бы пароли, а уж потом и ключи допилим.
В моем случае используются:
OS: Debian Squeeze
LDAP: Oracle DSEE7

Начнем с того, что сохраним в безопасное место файл nsswitch.conf, который каждая вражеская программа пытается изкорёжить:
cp /etc/nsswitch.conf /etc/nsswitch.conf.orig
chattr +i /etc/nsswitch.conf.orig

Теперь научим наш PAM работать с LDAP-сервером. Для этого нам понадобятся модуль PAM, которому собственно предстоит работать с LDAP, и модуль NSS ( Name Service Switch), который к этому LDAP доступ для PAM'а обеспечит. Ну и консольные LDAP-утилиты для удобство дебага проблем:
apt-get install ldapscripts libnss-ldapd libpam-ldapd
Прошу обратить внимание на букву "d" в конце слов libnss-ldapd и libpam-ldapd. Раньше эти модули назывались без нее: libnss-ldap и libpam-ldap. Но потом их улучшили и переименовали. Если ошибетесь и поставите старые, то будете сами конфигурировать nscd и nslcd. И вообще там больше геморроя. А новые модули сами все делают.

В процессе установки будут запрошены некоторые параметры (ваши ответы врядли совпадут с моими, так как у меня специфический LDAP-сервер, я просто привожу пример):
LDAP server Uniform Resource Identifier:
ldaps://ldap.nodesquad.com:1686/
Distinguished name of the search base:
dc=nodesquad,dc=com
* (напротив всех сервисов поставить галочки )


Нужно также учесть, что возможно пользователь впервые подключается к системе (ведь сисадмин не бегал по всем серверам и не создавал учетку с домашней директорией). Поэтому заставим PAM автоматически создавать такую директорию (припишем в самом низу):

vi /etc/pam.d/common-session


#Automatically create home direcory if not exist
session required          pam_mkhomedir.so skel=/etc/skel/ umask=0077


Для тех, кто хочет использовать аутентификацию по паролю, а их LDAP использует PosixAccount, на этом мануал будет закончен. Но у нас не все так сладко.

Для пользователей Oracle/SUN DSEE (Directory Server Enterprise Edition) придется каждому существующему пользователю добавить дополнительный класс и несколько его полей.
Как это сделать, написано тут.

Теперь можно пробовать приконнектиться по SSH со своим пользователем. Если не получилось, нужно смотреть логи запросов LDAP - как правило только они и спасают.

Если все ровно, то вот Вам новая проблема. Обратите внимание, что любая команда, подразумвающая обращение к LDAP (да хотябы ls, который показывает владельца файла) отвечает долго. Это из-за того, что она каждый раз обращается к LDAP. Во избежание этого нужно включить кэширование всех NSS-запросов: nscd. Но этому посвящено пол интернета, так что я обойду эту тему.

**********





Oracle/Sun DSEE 7 и его отношение к NIS-схеме. (posixAccount)

В Oracle/Sun DSEE по умолчанию нету атрибутов из NIS-схемы, а в частности класса объектов posixAccount, который бывает жизненно необходим, например для аутентификации SSH через LDAP). Но... он там есть! И спасибо большое статье Adding UNIX users to DS6. Я не смог найти как скачать DSE (похоже вражеский Oracle тупо убил этот проект после покупки Sun) или по другому заставить DSEE отображать все, что он умеет, а не только то, что популярно, но мы всегда можем добавить ручками для каждого существующего аккаунта объект posixAccount и его поля.
Подготовим LDIF:
tee > add.posixAccount.ldif

dn: uid=michael.abramovich,ou=people,dc=nodesquad,dc=com
changetype: modify
add: objectClass
objectClass: posixAccount
-
add: homeDirectory
homeDirectory: /home/michael.abramovich
-
add: uidNumber
uidNumber: 1007
-
add: gidNumber
gidNumber: 1007
-
add: loginshell
loginshell: /bin/bash

Ну а теперь вгружаем этот чендж:
ldapmodify -x -D 'cn=Directory Manager' -W -H ldap://:1389/ -f add.posixAccount.ldif