Posterous theme by Cory Watilo

Filed under: webtech

LAMP through fcgid with suexec

Взглянем на Apache+mod_php. Плюсы:

  • настраивается максимально просто
  • интерпретатор стартует вместе с каждым форком апача

Минусы:

  • mpm_prefork далеко не самый быстрый
  • все работает под одним пользователем (да-да, можно накрутить mod_itk)

В попытках сделать секьюрно и по возможности быстро я решил скрутить apache (mpm_worker) + mod_fcgid + suexec. Сам по себе CGI очень небыстр за счет того, что при каждом запросе подымается интерпретатор. FastCGI быстрее, так как интерпретатор держится отдельным процессом. А mod_fcgid – модуль, бинарно совместимый с mod_fastcgi, с новой стратегией управления процессами.

Suexec в свою очередь позволяет выполнять CGI/FastCGI/SSI с указанными uid/gid. Да-да, в системе будут заводиться реальные пользователи.

Расстановкой прав можно добиться того, что даже взломав один сайт злоумышленник не сможет увидеть другие сайты.

Приступим.

apt-get install apache2-mpm-worker apache2-suexec ache2-threaded-dev libapache2-mod-fcgid php5-cgi

Теперь когда у нас все есть, создадим скелет для будущих площадок.

mkdir -p /root/web/skel
cd /root/web/skel
mkdir {cgi-bin,etc,logs,tmp,www}
chmod 770 tmp
chmod 751 {etc,logs,www}
cp /etc/php5/cgi/php.ini ./etc

Правим php.ini на предмет вывода ошибок и прочих мелких твиков.

Теперь сделаем враппер для самого обработчика (cgi-bin/php-cgi):

#!/bin/bash

cd $CGI_BIN_DIR
PHP_INI=../etc/php.ini

if [ ! -f $PHP_INI ]; then
    PHP_INI=/etc/php5/cgi/php.ini
fi

exec /usr/bin/php5-cgi -c $PHP_INI

И делаем

chown -R 755 cgi-bin
cd /root/web
touch {adduser,awstats,vhost}.skel
touch add_site.sh && chmod +x add_site.sh

adduser.skel:

DSHELL=/bin/false
DHOME=/var/www
GROUPHOMES=no
LETTERHOMES=no
SKEL=/root/web/skel
FIRST_SYSTEM_UID=2000
LAST_SYSTEM_UID=2999
FIRST_SYSTEM_GID=2000
LAST_SYSTEM_GID=2999
FIRST_UID=2000
LAST_UID=29999
FIRST_GID=2000
LAST_GID=2999
USERGROUPS=yes
USERS_GID=100
DIR_MODE=0751
SETGID_HOME=no
QUOTAUSER=""
SKEL_IGNORE_REGEX="dpkg-(old|new|dist)"

awstats.skel:

LogFile="/var/www/#USER/logs/#SITE-access.log"
SiteDomain="#SITE"
HostAliases="localhost 127.0.0.1 REGEX[#SITE$]"
Include "/etc/awstats/awstats.conf.local"

vhost.skel:

<virtualhost *>
    ServerName #SITE
    ServerAlias www.#SITE
    DocumentRoot /var/www/#USER/www/#SITE/public_html

    SuexecUserGroup #USER #USER

    ScriptAlias /cgi-bin/ /var/www/#USER/cgi-bin/
    <directory /var/www/#USER/www/#SITE/public_html>
    Options -Indexes +ExecCGI
    AllowOverride All
    AddHandler fcgid-script .php
    FCGIWrapper /var/www/#USER/cgi-bin/php-cgi .php
    Order allow,deny
    Allow from all
    </directory>

    ErrorLog /var/www/#USER/logs/#SITE-error.log
    CustomLog /var/www/#USER/logs/#SITE-access.log combined

    SetEnv AWSTATS_FORCE_CONFIG #SITE
    <location /cgi-bin/awstats.pl >
    AuthUserFile /var/www/#USER/etc/awstats.passwd
    AuthName "Website stats for #SITE"
    AuthType Basic
    require valid-user
    </location>
</virtualhost>

add_site.sh:

#!/bin/bash

if [ -z $1 ] || [ -z $2 ]; then
    echo "Oops. Some param not given."
    exit 1
fi

# If no such user exists - add one right now
if [ ! -d /var/www/$2 ]; then
    adduser --conf ./adduser.skel --disabled-login --gecos '' $2 || exit 1
fi

# Buld generic folders
mkdir -p /var/www/$2/www/$1/public_html
chown -R $2:$2 /var/www/$2/www/$1

# Build generic vhost for apache
cat vhost.skel | sed "s/#USER/${2}/g;s/#SITE/${1}/g" > ${2}_${1}

# Activate vhost
a2ensite ${2}_${1}

# Build generic awstats config
cat awstats.skel | sed "s/#USER/${2}/g;s/#SITE/${1}/g" >> /etc/awstats/awstats.${1}.conf

echo "Restart apache..."
apache2ctl configtest && apache2ctl restart

Вот таким нехитрым образом можно добавить сайт:

cd /root/web
./add_site example.org web_example

В результате будет создан пользователь web_example и у него сайт – example.org.

Tip

Иногда надо позволить скрипту выполяться боее 30 секунд. Думаете для этого достаточно подправить php.ini? Нет, в таком случае fcgid отстрелит скрипт по достижении 40 секунд. Для этих случаев в конфиге vhost’а требуется задать значение IPCCommTimeout в секундах (например, 300). Есть баг – глобальное значение отчего-то не хочет применяться к vhost’у, потому надо указать его непосредственно в vhost’е.

В общем и целом – конфиги показал, идею донес (надеюсь). Enjoy.

LVM Rocks

Давно заметил что в Ubuntu (server edition) во время инсталляции начали предлагать использовать LVM. Но я все не решался поставить production на него. Затем пообщался со теми кто его использовал, почитал доку - и последний год стал его использовать, так как постиг скрытую в нем мощь :) Допустим, у нас есть простенький бюджетный сервер. Мы развернули новое приложение, и его база стала расти весьма стремительно. Итого - база, веб-файлы и система живут на одном физическом диске. Был куплен диск WD Razor, и на него перенесли базу. Нагрузка диска (iostat -x -m 1) составила 1-2%. Решено перенести туда же и веб-файлы, однако решение это пришло лишь через пару дней. Так что получилось наглядно продемонстрировать возможности LVM.

Part1. Creating...

Когда поставили разор на нем создали один раздел - LVM: [cc lang="bash"] # fdisk -l Disk /dev/sdb: 74.3 GB, 74355769344 bytes 255 heads, 63 sectors/track, 9039 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Disk identifier: 0x00000000 Device Boot Start End Blocks Id System /dev/sdb1 1 9039 72605736 8e Linux LVM [/cc] Пару слов об организации LVM. Уровень 1: volume group (vg). Это наивысший уровень абстракции, объединяющий в себе logical volumes и physical volumes. Уровень 2: physical volume (pv). Это некое блочное устройство, способное хранить данные (HDD, RAID, ...) Уровень 3: logical volume (lv). Это эквивалент раздела на жестком диске. Таким образом в группу добавляются физические тома (pv), и потом во всем этом пространстве свободного места создаются разделы (lv), на которых уже создается файловая система. Итак, сначала создавался pv: [cc lang="bash"] pvcreate /dev/sdb1 [/cc] Затем vg: [cc lang="bash"] vgcreate sys_vg /dev/sdb1 [/cc] И затем на все свободное место указанного pv (/dev/sdb1) создали lv с именем var_lib_mysql: [cc lang="bash"] lvcreate -L 100%PVS -nvar_lib_mysql sys_vg /dev/sdb1 [/cc] Дело за малым: [cc lang="bash"] mkreiserfs /dev/mapper/sys_vg-var_lib_mysql mount /dev/mapper/sys_vg-var_lib_mysql /mnt /etc/init.d/mysql stop mv /var/lib/mysql/* /mnt/ mv /var/lib/mysql/.* /mnt/ umount /mnt mount /dev/mapper/sys_vg-var_lib_mysql /var/lib/mysql chown -R mysql:mysql /var/lib/mysql /etc/init.d/mysql start [/cc] Вот собственно и почти все. Последний штрих - прописать монтирование раздела в fstab, дабы это происходило при загрузке автоматом. Можно скучно сделать это через blkid, увидеть там нужный UUID (например, f6946e54-c7d6-4688-8fac-05dcb1bf9973), скопировать его, открыть /etc/fstab и вставить туда строку вида: [cc lang="bash"] UUID=f6946e54-c7d6-4688-8fac-05dcb1bf9973 /var/lib/mysql reiserfs defaults 0 2 [/cc] а можно сделать так: [cc lang="bash"] printf "\nUUID=`blkid | grep sys_vg-var_lib_mysql | sed -r 's/.*UUID="([^"]*).*/\1/i'`\t/var/lib/mysql reiserfs defaults 0 2\n" >> /etc/fstab [/cc] Да, если сделать umount /var/lib/mysql && mount /var/lib/mysql до ребута - то /dev/disk/by-uuid/f6946e54-c7d6-4688-8fac-05dcb1bf9973 (или какой там получится) там еще не будет. Для того чтоб появился до ребута надо перезапустить udev: [cc lang="bash"] /etc/init.d/udev restart [/cc]

Part2. Resizing...

Как я говорил раньше, с опозданием пришла мысль о том, что неплохо бы вынести и статические файлы на этот же винт. И сделать это совсем просто! Для этого от того lv что был создан раньше (и именуется var_lib_mysql) откусим немного места. Сначала остановим все службы (говорят, reiserfs увеличивается/уменьшается без проблем налету, но я этого пока не пробовал на себе): [cc lang="bash"] /etc/init.d/mysql stop umount /var/lib/mysql [/cc] Затем уменьшим файловую систему, а затем и lv на 20ГБ: [cc lang="bash"] resize_reiserfs -s-20G /dev/mapper/sys_vg-var_lib_mysql lvreduce -L-20G /dev/mapper/sys_vg-var_lib_mysql [/cc] На всякий случай я предпочел проверить фс на ошибки (а вдруг!): [cc lang="bash"] reiserfsck /dev/mapper/sys_vg-var_lib_mysql [/cc] Ну и возвращаем обратно MySQL: [cc lang="bash"] mount /var/lib/mysql /etc/init.d/mysql start [/cc] Теперь создадим lv для веб-файлов, и так как их намного меньше 20ГБ, я решил оставить 5ГБ про запас, никому их не присвоив. Потом можно будет налету добавить туда где закончится место. [cc lang="bash"] lvcreate -L 15G -nvar_www sys_vg mkreiserfs /dev/mapper/sys_vg-var_www [/cc] Далее - перенос файлов: [cc lang="bash"] /etc/init.d/nginx stop /etc/init.d/apache2 stop mount /dev/mapper/sys_vg-var_www /mnt mv /var/www/* /mnt/ mv /var/www/.* /mnt/ umount /mnt mount /dev/mapper/sys_vg-var_www /var/www /etc/init.d/apache2 start /etc/init.d/nginx start [/cc] И опять не забываем про fstab: [cc lang="bash"] printf "\nUUID=`blkid | grep sys_vg-var_www | sed -r 's/.*UUID="([^"]*).*/\1/i'`\t/var/lib/mysql reiserfs defaults 0 2\n" >> /etc/fstab [/cc] По материалам:
  1. http://wiki.linuxquestions.org/wiki/LVM#example
  2. http://www.tldp.org/HOWTO/LVM-HOWTO/reducelv.html
PS: после изменения размера мог измениться UUID для lv var_lib_mysql, хотя я и не уверен в этом. Но проверить не помешает. PS2: если работаете удаленно - не забывайте про screen. PS3: писалось по памяти, так что могут быть некоторые неточности. Тупой копипаст без вовлечения мыслительного процесса чреват боком. Я предупредил ;)

Lighttpd and execwrap

В качестве вэб-сервера я использую lighttpd. И сегодня я наткнулся на старую проблему - вэб-сервер работает из-под пользователя www-data, и когда пользователь pupkin загружает какой-то файл - этот файл принадлежит не pupkin'у, а www-data. И через ftp удалить его он не может, и вынужден искать некие web-file-manager'ы. Так вот я погуглил и наткнулся на execwrap. Он очень мелкий, почти ничего ему не нужно стороннего для работы, конфигурировать и использовать его крайне просто (да-да, я знаю про suExec, но он мне не сильно понравился). В результате конфиг для сайта у меня выглядит так: [cc lang="text"] $HTTP["host"] == "pupkin.com" { server.name = "pupkin.com" site-root = "/home/pupkin/www/" + server.name server.document-root = site-root + "/www" accesslog.filename = site-root + "/log/access.log" setenv.add-environment = ( "AWSTATS_FORCE_CONFIG" => server.name ) auth.backend.htpasswd.userfile = site-root + "/awstats.passwd" fastcgi.server = ( ".php" => (( "bin-path" => "/var/www/lighttpd/execwrap", "socket" => "/tmp/" + server.name + ".phpsocket", "max-procs" => 10, "bin-environment" => ( "UID" => "9999", "GID" => "9999", "TARGET" => site-root + "/cgi-bin/php", "CHECK_GID" => "1", "CGI_BIN_DIR" => site-root + "/cgi-bin/", "PHP_FCGI_CHILDREN" => "2", "PHP_FCGI_MAX_REQUESTS" => "5000" ) ))) } [/cc] где 9999 - ID пользователя и группы (у меня они часто совпадают). А в директории /home/pupkin/www/pupkin.com/cgi-bin лежит два файла - симлинк на php.ini и исполняемый файлик "php" следующего содержания: [cc lang="bash"] #!/bin/bash cd $CGI_BIN_DIR exec /usr/bin/php-cgi -c ./php.ini [/cc] Как видите, если надо то конфиг php.ini можно пользователю задать персональный. А файлик php обязательно должен принадлежать этому же пользователю. Когда все сделано - рестартуем lighttpd и смотрим, нет ли в логах ругательств с кодом ошибки 22 - это, как видно из сорса execwrap, несоответствие прав. Enjoy =) UPD: execwrap умер и воскрес тут: http://cgit.stbuehler.de/gitosis/execwrap/

PHP Optimizers revisited

Только-что наткнулся на эту публикацию, в которой основные ссылки - на сорс и на результат. Как и показали результаты, стоит особо присмотреться к APC. Лично я еще после первого тестирования перешел на него. UPD: есть нарекания на конфиг XCache в приведенном тесте, но у меня он страшно "лагал" с правильным конфигом, так что я свой выбор не поменял.

Search for bug tracker

Помогите люди добрые советом при выборе багтракера. Из необходимого:
  • многопроектность (участники одного проекта не должны даже догадываться о существовании других проектов... пока им не дадут туда доступ)
  • управление пользователями и правами
  • интуитивно понятный интерфейс
  • Wiki
  • Отправка уведомлений почтой
Из желаемого - time tracking, связка с SVN/BZR/Mercurial, работа с тикетами через почту. Рассмотрены:
  • mantis - интерфейс аццки калечный
  • bugzilla - интерфейс хромает
  • Trac - все изумительно, но многопроектность...
  • RT - громоздкая. Весьма. Задолбался пока поставил и настроил. Удалилось быстрее.
UPDATE:
  • Eventum from MySQL - весьма громоздко, неудобно. Раньше были траблы с русским, но верно уже все с этим в порядке.
  • SimpleTicket - сырющая, неюзабельно глюкающая системка. Простая... и совсем без ничего.
  • Trac - надо попробовать мультипроектность через плагин.
  • TrackStudio - выглядит заманчиво, но стоит денег.
Пока остановился на RedMine.