Генеральный спонсор: Хостинг «Джино»

Система Orphus
Russian version
Добавить на Del.icio.us
English version
Добавить на Digg.com

 dkLab | Конструктор | dkLab Apache: виртуальные хосты с привилегиями различных пользователей 

Карта сайта :: Форум «Лаборатории» :: Проект «Денвер»
Проект «Orphus» :: Куроводство: наблы :: Конструктор


2007-02-31

Скачать dkLab Apache:
dklab_apache_34_rew_lim_rus_ssl_vh_fork_2005-12-04.tgz

dkLab Apache - это дистрибутив для тех, кто собирается использовать Apache в Unix (Linux, FreeBSD и т. д.) для обслуживания нескольких независимых сайтов, работающих под разными, полностью разграниченными друг от друга пользователями Unix. Он лишен некоторых недостатков, присущих аналогам, например: не требует установки дополнительных модулей ядра и запретов setuid, корректно и, главное, осмысленно работает при включенном KeepAlive и превышающем единицу MaxRequestsPerChild.

По сути это Apache 1.3.34, на ядро которого наложены некоторые "самодельные" патчи. Вот функциональность, которую они добавляют:

  • Запуск различных виртуальных хостов под различными Unix-пользователями. То, под каким пользователем работает виртуальный хост, задается в его стандартных директивах User и Group. Все скрипты, включая скрипты для mod_php, CGI и т. д., работают с правами указанного пользователя и группы и не могут получить доступ к файлам другого виртуального хоста. Долой safe_mode и проблемы с правами доступа в PHP!
  • Возможность создавать виртуальные хосты по шаблону: abc.example.com -> /home/example/abc. Вы можете ссылаться в директиве DocumentRoot на нужную часть доменного имени, например, так: /home/example/$-3+ (в данном примере это "развернется" в /home/example/abc). Просто создайте директорию, чтобы добавить на сайт новый поддомен!
  • Модуль mod_rewrite защищен от любого рода "зацикливаний". Неосторожно или злонамеренно написанные директивы в .htaccess не смогут захватить все ресурсы CPU и "подвесить" сервер.

В дистрибутив включены также следующие популярные модули:

  • Модуль mod_limitipconn для возможности ограничения числа одновременных соединений с одного IP-адреса.
  • Модуль mod_charset - "Russian Apache", поддержка работы с русскоязычными кодировками.
  • Модуль mod_ssl - поддержка протокола SSL.

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

Внимание! Если вы обнаружили баг в dkLab Apache, или у вас есть просто какое-то замечание, то прежде, чем сообщать о нем в форуме, прочитайте раздел Известные баги и замечания. Возможно, там проблема уже описана. Также, если вы - опытный Си-Unix программист, и у вас есть какие-то идеи по исправлению багов или улучшению дистрибутива, я буду рад активному (с вашей стороны) сотрудничеству, и в особенности - присланному коду.

Использование

dkLab Apache не определяет каких-либо новых директив конфигурации, и его использование должно быть интуитивно понятным. Например, вот так вы можете завести 2 виртуальных хоста, работающих под разными Unix-пользователями даже с mod_php:

Листинг 1
# Limit by-clients resource usage .
RLimitCPU 12 12
RLimitNPROC 15 15
RLimitMem 20000000 20000000

# You may freely enable these directives, wow!
MaxRequestsPerChild 100
KeepAlive On

# Limit the number of connections (standard mod_limitipconn directives).
<Files ~ ".*">
  MaxConnPerIP 15
  NoIPLimit image/*
</Files>

# Account dklab.ru
<VirtualHost *:80>
    ServerName dklab.ru
    ServerAlias dklab.ru *.dklab.ru
    User dklab
    Group dklab
    # If not present, $-3+ is interpolated to "www" string.
    DocumentRoot /home/dklab/domains/$-3+
    ScriptAlias /cgi/ /home/dklab/domains/$-3+/../../cgi/
    ErrorLog /home/dklab/account/log/error_log
    CustomLog /home/dklab/account/log/access_log common
</VirtualHost>

# Account denwer.ru
<VirtualHost *:80>
    ServerName denwer.ru
    ServerAlias denwer.ru *.denwer.ru
    User denwer
    Group denwer
    DocumentRoot /home/denwer/domains/$-3+
    ScriptAlias /cgi/ /home/denwer/domains/$-3+/../../cgi/
    ErrorLog /home/denwer/account/log/error_log
    CustomLog /home/denwer/account/log/access_log common
</VirtualHost>

Данный пример иллюстрирует сразу 2 операции: привязку виртуального хоста к выделенному пользователю и группе, а также определение "динамического" DOCUMENT_ROOT, вычисляемого на основе доменного имени. Здесь $-3+ означает "отсчитать часть справа ($-) от третьей (3) части доменного имени и дальше (+) и вставить в указанную позицию". Этот синтаксис подробнее описан в документации к модулю mod_vhost_alias (только учтите, что вместо % в dkLab Apache используется $).

Обратите еще внимание на то, что если выражению $-3+ не соответствует никакая часть доменного имени, то вместо пустой строки подставляется "www". Например, это верно для имени dklab.ru в примере выше - у него нет "третьей справа части", а потому запросы к dklab.ru идут в /home/dklab/domains/www - так же, как и www.dklab.ru.

Недостатки стандартного Apache

Лирическое отступление 
Без малого 5 лет в моем распоряжении находится "самописный" инструмент, которому я пока что так и не нашел сколько-нибудь удовлетворительую альтернативу. Этот инструмент не идеален, однако он все же работает, поэтому сегодня я хотел бы поделиться им. Я все ждал, когда же весь мир перейдет на технологию виртуальных машин, или же разграничение пользователей реализуют сами разработчики Apache, но сейчас все же решил покончить с ожиданиями и опубликовать то, что у меня есть.

Apache - один из наиболее популярных web-серверов в мире. Однако приходится констатировать, что при организации массового хостинга на этом сервере вскрывается ряд его существенных недостатков, в числе которых:

  1. Невозможность полного разграничения пользователей по виртуальным хостам. Например, хостинг-провайдер может иметь на одной и той же машине несколько сотен различных сайтов, принадлежащих различным пользователям (клиентам). Один пользователь не должен иметь возможность как-либо вмешаться в работу другого: например, он не имеет права читать чужие файлы. В Apache предусмотрен механизм suEXEC, однако он работает только для CGI-скриптов и полностью неприменим, например, к mod_php (одному из самых популярных скриптовых модулей для Apache в мире).
  2. Невозможность подмены DOCUMENT_ROOT для "шаблонных" виртуальных хостов. Обычно клиенты хотят иметь возможность заводить на своих сайтах домены третьего уровня, не обращаясь при этом к хостеру. Идеальный вариант выглядит как "владелец example.com создал папку abc и тут же получил домен abc.example.com с DOCUMENT_ROOT в этой папке". Apache, конечно, имеет возможность назначать папки доменам вида *.example.com, однако DOCUMENT_ROOT при этом остается неверным. (Даже написав модуль Apache 1.3, нельзя "подменить" эту переменную окружения.)
  3. Проблема с "зацикливаниями" mod_rewrite. Начиная с некоторой версии, модуль mod_rewrite имеет механизм защиты от "зацикливаний" правил (раньше такие "зацикливания" вели к полному зависанию сервера с потреблением 100% ресурсов CPU). К сожалению, эта защита до сих пор не полна: существуют методы все так же "подвесить" Apache, что совершенно недопустимо в контексте массового хостинга (один клиент не должен иметь возможность "убить" весь сервер).

Уже многие годы разработчики Apache "закрывают глаза" на все эти недостатки. Даже в версии 2 не наметилось сколько-нибудь значимых продвижений. Складывается впечатление, что они и не подозревают, что кто-то использует Apache для обслуживания множества сайтов, принадлежащих независимым клиентам.

Чайник 

В то же время, каждый мало-мальски крупный хостинг-провайдер добавляет поддержку "многопользовательской" работы самостоятельно, но категорически отказывается делиться полученным результатом с общественностью. Видимо, это коммерческая тайна; к тому же, если в патче будет найдена уязвимость, под угрозу ставится весь хостинг. К счастью, я не работаю в хостинг-компании, а потому делюсь.

По каждому из приведенных выше пунктов имеются свободно распространяемые альтернативы. К сожалению, они все далеки от идеала:

  1. Запуск виртуальных хостов под разными пользователями реализуется в модулях: perchild, peruser, mod_diffpriv, mod_suid и некоторых других. Увы, они все имеют недостатки: необходимость отключать KeepAlive, либо же устанавливать MaxRequestsPerChild равным 1, либо же устанавливать дополнительные модули ядра и искусственным образом ограничивать доступ к setuid-функциям.

    Чайник 

    Директива KeepAlive разрешает Apache обрабатывать запросы к нескольким URL в рамках одного HTTP-соединения. Например, если вы зашли на страницу с 10 картинками, то будет открыто всего 1 соединение с сервером, в которое поступит 11 запросов: 1 - для HTML-кода страницы и 10 - для картинок. Если KeepAlive отключен, браузер будет вынужден открыть 11 соединений, что в несколько раз замедлит отображение страницы. Директива MaxRequestsPerChild разрешает одному и тому же демону Apache обратывать последовательно несколько пришедших соединений. Таким образом экономится на системных вызовах fork: нет нужды создавать новые процессы, когда происходит очередное соединение.

  2. Возможность "динамического связывания" доменов третьего уровня с различными директориями реализрована, например, в модуле mod_vhost_alias. К сожалению, он не может менять DOCUMENT_ROOT, так что "шаблонные" хосты будут не такими же полноправными, как и основной хост.
  3. Как уже говорилось выше, стандартный mod_rewrite борется с "зацикливаниями", но лишь частично.

Как работает dkLab Apache?

Дистрибутив dkLab Apache лишен описанных выше недостатков. (Правда, у него есть свои собственные, о которых поговорим ниже.)

Лирическое отступление 
В коде я старался выделить все основные изменения в ядре Apache тэгами <dk>...</dk>. Если вы хотите посмотреть код, имеет смысл вначале поискать в нем именно данные тэги как подстроки.

Запуск виртуальных хостов под различными пользователями

Схема обработки запроса в dkLab Apache выглядит так.

  1. При старте демон, слушающий запросы, выполняет системный вызов fork и ждет завершения потомка, передавая ему все поступающие сигналы.
  2. Как только происходит очередное соединение, его перехватывает дочерний процесс демона.
  3. Он разбирает заголовки самого первого запроса в рамках этого соединения и определяет, какому виртуальному хосту следует заняться обработкой.
  4. Демон вызывает setgroups, setgid и setuid и продолжает обработку всех запросов до закрытия соединения.
  5. Если дочерний процесс-демон "умирает", родительский демон также завершает свою работу.

В общем-то, это достаточно "стандартная" схема для такого рода модулей, за одним важным исключением: вызов fork происходит не в момент установления нового соединения, а в момент инициализации нового слушающего экземпляра Apache, причем - уже после большинства ресурсоемких инициализаций, которые выполняет любой демон Apache при старте. Таким образом, при поступлении нового запроса fork не выполняется, что значительно ускоряет работу. Кроме того, мы можем свободно использовать отличный от единицы MaxRequestsPerChild, и это дает значительный прирост производительности! Ведь при MaxRequestsPerChild, равном 1, демон Apache вынужден каждый раз выполнять все свои стартовые инициализации, в то время как в dkLab Apache при MaxRequestsPerChild, большем 1, цикл обработки соединений выполняется уже "после" первой инициализации демона и выполнения fork.

Лирическое отступление 
Возможно, вы уже задались вопросом: во сколько примерно раз MaxRequestsPerChild=1 медленнее, чем, скажем, MaxRequestsPerChild=100. Ответ - в десятки раз! Если не верите, проведите свои собственные тесты и убедитесь, что решение, требующее обязательного MaxRequestsPerChild=1, вообще неработоспособно при сколь-нибудь высокой нагрузке.

Конечно, эта схема имеет недостатки: если в одном и том же соединении произойдут обращения к двум виртуальным хостам, принадлежащим разным пользователям, второе обращение закончится ошибкой. Т.е. в рамках одного соединения мы можем обрабатывать запросы только к одному и тому же виртуальному хосту. К счастью, все популярные браузеры работают именно так. (Исключение составляют поисковые роботы, однако, как показала практика, они также прекрасно "переваривают" данную ошибку и пытаются выполнить запрос в новом соединении.)

Насколько ресурсоемок этот алгоритм? Ведь, фактически, он означает, что на каждое соединение система выполняет лишний вызов fork. Безусловно, это замедляет работу, однако, если вы используете dkLab Apache для организации хостинга, а не для отдачи исключительно статических данных сотнями милионов штук в день, все будет работать нормально, тем более что:

  • Fork выполняется асинхронно, как это описано выше, и потому не "торозит" сам запрос. Из-за этого визуально задержки не заметно вообще.
  • По сравнению со стандартными издержками на скриптовые языки потери от лишнего fork почти растворяются. Например, мне известно, что данный дистрибутив до сих пор успешно работает на довольно слабой машине 2xP1000 с 1 ГБ памяти и 2.5 млн хитов в сутки (больше 500 хостинг-пользователей с mod_php, perl, ssi и т. д.).
  • В современных Unix-системах fork работает очень быстро, т.к. не копирует в реальности всю память процесса. Копирование происходит лишь в момент изменения той или иной страницы памяти (технология copy-on-write).
  • Fork выполняется не для каждого запроса, а для каждого KeepAlive-соединения. Т.к. в типовом хостинге 90% запросов приходится на статические файлы (картинки), которые отдаются в том же соединении, что и сама страница, fork запускается в среднем на 1 раз на 10 запросов (к тому же - асинхронно).

Чайник 

Если вы отдаете статику десятками миллионов штук в день, значит вы - не хостер, а какой-то большой единичный проект, и запуск виртуальных хостов под разными пользователями вам совершенно ни к чему. Используйте стандартный Apache.

Виртуальные хосты "по шаблону"

Мои исследования показали, что возможность полноценно "подменять" DOCUMENT_ROOT в зависимости от доменного имени невозможно реализовать при помощи написания "чистого" модуля Apache. Поэтому изменения вносились непосредственно в ядро, там, где определяется переменная окружения DOCUMENT_ROOT (а также еще кое-где).

Собственно, эти шаблоны можно использовать не только в директиве DocumentRoot, но также и в любой другой, работающей с путями - например, ScriptAlias, как продемонстрировано в примере выше.

Модуль mod_rewrite, свободный от зацикливаний

Стандартное решение mod_rewrite предотвращает только от зацикливаний при редиректах. Однако, если, например, запущена команда RewriteRule . - [N], оно не спасает.

Патч в составе dkLab Apache решает проблему. Ради справедливости нужно заметить, что основная идея и первая реализация патча для mod_rewrite принадлежит Дмитрию Мельнику. Я лишь доработал ее и внедрил в dkLab Apache. Смысл заключается в том, что в цикл обработки директив mod_rewrite вставляется инкремент некоторого счетчика, при переполнении которого обработка завершается и в логи сервера записывается сообщение об ошибке. Это гарантировано не дает "подвесить" весь Apache одному конкретно взятому пользователю.

Известные баги и замечания

Естественно, dkLab Apache не идеален. Он просто вполне сносно работает (как водится, ничего при этом не гарантируя). Вот известные на данный момент проблемы, работа над решением которых ведется в вялотекущем режиме (и уже для Apache 2.2).

  • Увеличенное потребление ресурсов: добавляется 1 лишний fork на каждое поступающее KeepAlive-соединение. (Этот fork выполняется, впрочем, в фоновом режиме, а не при поступлении соединения, что сильно ускоряет работу по сравнению с другими решениями.) К сожалению, даже в архитектуру Apache 2.2 не закладывали никаких средств для распределения запросов по динамически создаваемым Apache-процессам, поэтому уход от этого ограничения в обозримом будущем не предвидится.
  • Отдача "пустого" ответа при запросе к двум разным виртуальным хостам в рамках одного соединения. Опять же, на практике это событие невозможно в браузерах, а поисковики достаточно умны, чтобы через некоторое время повторить запрос. (По хорошему, вместо пустого ответа должны приходить специальные заголовки - Retry-After: 0, Refresh: 0 и 503 Service unavailable, но у меня все не доходят руки это реализовать. Если сделаете - пришлите, пожалуйста, патч мне.)
  • В некоторых директивах mod_rewrite изредка "всплывают" макросы вида $-3+ при явных подстановках %{DOCUMENT_ROOT} в выражения. Скорее всего, вы с этим не столкнетесь, т.к. случаи уж очень экзотические. Но если столкнетесь, то вот один их способов обхода: создать символьную ссылку с именем $-3+ в папке, где расположена директория документов виртуального хоста.
  • Иногда обнаруживались ситуации, когда после продолжительной работы один из дочерних процессов демона Apache (httpd) "отщеплялся" от корневого процесса httpd и становится потомком init. Сейчас с достоверной точностью нельзя сказать, был ли это демон, реально обрабатывающий запрос, или его "ждущая пара". Но можно определенно сказать одно: если каждую ночь после ротации логов устраивать полный перезапуск apache, данная проблема не повторяется никогда. (Я подозреваю, что тут беда с каким-то пропущенным перехватом longjmp. Вообще-то, патч перехватывает вызовы longjmp, но, возможно, не все. Я не смог больше ничего раскопать. И еще одно: в Apache 2.2 никаких longjmp нет, поэтому данная проблема там, скорее всего, не могла бы возникнуть даже теоретически.)
  • Sometimes after very long Apache uptime you may watch a "chip off" httpd daemon as a child of init process. Seems there is a bug in setjmp/longjmp processing somewhere (jongjmp is handled in dkLab Apache, but possibly not fully successful). But if you restart Apache nightly after log rotation you will be guaranteed avoided of this problem.
  • Поставляется дистрибутив Apache с уже наложенными патчами, а не только сами эти патчи. Увы, так получилось исторически - выделить отдельный патч сейчас довольно трудно, т.к. mod_ssl также накладывается на дистрибутив Apache в виде патча, и отличить одно от другого - большая работа. Впрочем, если вы это сделаете и пришлете сюда получившийся патч, все будут вам только благодарны.
  • Пока что dkLab Apache работает только с Apache 1.3, однако в бущущем серьезно развивать эту ветку не планируется. Вместо этого разработка должна сосредоточиться на Apache 2.2 - у него значительно более стройная внутренняя архитектура.

Резюме

Если вы собираетесь открыть собственный хостинг, но не хотите при этом связываться с cPanel и другими системами, разграничивающими хостинг-пользователей "абы-как", dkLab Apache позволит вам легко это сделать. Он также пригодится, если вы имеете несколько различных проектов на одной машине, но не хотите, чтобы взлом одного из них ставил под угрозу другой.

По факту dkLab Apache достаточно стабилен, чтобы назвать его production-версией: на нем уже много лет работают несколько нагруженных серверов (в частности, сервер dklab.ru).

И последнее. Примерно четыре года назад я изложил некоторые идеи, положенные в основу dkLab Apache, в статье. Эта статья пролежала в открытом доступе всего 2 недели (после чего хостинг-провайдер, использующий в то время патч, попросил ее убрать), но и за это время она "утекла" на множество других серверов. Там, кстати, была уязвимость в безопасности, которая уже давным-давно исправлена в dkLab Apache. Так что - пользуйтесь на здоровье, на этот раз - готовым дистрибутивом!







Дмитрий Котеров, Лаборатория dk. ©1999-2014
GZip
Добавить на Del.icio.us   Добавить на Digg.com   Добавить на reddit.com