Fork me on GitHub

The WebDevil

Enjoy development

  1. Login to the router via ssh.
  2. Type in nvram set http_passwd=
  3. Then type in nvram commit

taken from Righteous Hack

Monitoring apache with GOD

November 30th, 2009

“Посчастливилось” мне заполучить VPS с сайтом на битриксе. Из-за Zend Optimizer (сорсы покриптованы) нет возможности установить APC. Вроде бы умеет xcache работать вместе с ним, еще не проверял. Периодически апач съедает всю память, свап, и машинка умирает окончательно. Хуже то, что апач работает с mpm-prefork, то есть он порождает не потоки, а процессы. И при мониторинге виден родительский процесс, который занимает всего 13М памяти.

Вначале для рестарта апача в период дикого роста я пытался использовать monit, но результат был неадекватный – при указании memory > 90% он убивал его когда памяти была занята всего треть.

После monit я попробовал использовать god. Но он мониторит только один процесс, без его чайлдов. Вроде бы есть проект bluepill, который идейно растет из god. Но примеры найти оказалось проблематично, да и в процессе решил таки остаться с god.

Среди условий у god была обнаружена фича – :lambda. После недолгого размышления на bash была написана строка, показывающая занятый апачем процент памяти:

ps -e -o pmem,cmd | grep apache2 | grep -v grep | awk '{sum += $1;}END{print sum;}'

что сразу переродилось в

w.restart_if do |restart|
  restart.condition(:lambda) do |c|
    c.lambda = lambda{`ps -e -o pmem,cmd | grep apache2 | grep -v grep | awk '{sum += \$1;}END{print sum;}'`.to_i > 90}
  end
end

Итого конфиг для апача выглядит так:

God::Contacts::Email.delivery_method = :sendmail

God.contact(:email) do |c|
  c.name = 'maintainer'
  c.email = 'pager@sms.gate.isp'
end


God::Contacts::Email.format = lambda do |name, email, message, time, priority, category, host|
  < <-EOF
From: god
To: #{name} <#{email}>
Subject: Alert!
Date: #{Time.now.httpdate}
Message-Id: < #{rand(1000000000).to_s(36)}.#{$$}.#{self.message_settings[:from]}>

#{host} (#{priority}): #{message}
  EOF
end


%w{80}.each do |port|
  God.watch do |w|
    w.name = "apache2"
    w.pid_file = "/var/run/apache2.pid"
    w.interval = 10.seconds # default
    w.start = "/etc/init.d/apache2 start"
    w.stop = "/etc/init.d/apache2 stop"
    w.restart = "/etc/init.d/apache2 restart"
    w.start_grace = 10.seconds
    w.restart_grace = 20.seconds

    w.start_if do |start|
      start.condition(:process_running) do |c|
          c.interval = 5.seconds
          c.running = false
      end
    end
    w.restart_if do |restart|
      restart.condition(:lambda) do |c|
        c.lambda = lambda{`ps -e -o pmem,cmd | grep apache2 | grep -v grep | awk '{sum += \$1;}END{print sum;}'`.to_i > 90}
        c.notify = 'maintainer'
      end
    end
  end
end

LAMP through fcgid with suexec

August 11th, 2009

Взглянем на 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. Да-да, в системе будут заводиться реальные пользователи.

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

Exim mail forwarding

August 11th, 2009

Стоял у меня MTA Exim4 для отправки почты. И вдруг на этот сервер перенесли MX-запись одного домена (например, example.org), и он вынужден был что-то делать с почтой. Решено было перенаправить всю почту для этого домена на другой ящик на время разбирательств кто прав, а кто виноват.

Для этого в Ubuntu пришлось сделать следующие правки.

В /etc/exim4/update-exim4.conf.conf в dc_other_hostnames и dc_relay_domains был добавлен example.org.

А сам форвард выглядел так (/etc/exim4/conf.d/router/099_exim4-config_redirects):

forward:
    driver = redirect
    domains = example.org
    data = tmp_example.org@somemail.com

В теории должна была сработать запись вида

sender_redirect:
  driver = redirect
  data = ${lookup{$sender_address}lsearch{/etc/exim4/sender_redirects}}
  domains = example.org

И в файле /etc/exim4/sender_redirects были бы

someone@example.org: where.to@forward.com

Но почему-то этот вариант не сработал как ожидалось.

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

Столкнулся сегодня с проблемой: переменные, установленные через SetEnv в апаче не видны в cgi-скриптах.
Поиски произростания ног привели к suEXEC, который фильтрует набор переменных окружения. И, естественно, в списке разрешенных нет ничего про AWSTATS_FORCE_CONFIG.
Проблему решил следующим образом:

mkdir ~/build_suexec && cd ~/build_suexec
wget http://svn.apache.org/repos/asf/httpd/httpd/trunk/support/suexec.{c,h}

Далее в suexec.c находим внутри большой список переменных, и добавляем туда то, что нужно:

# diff ./suexec.c.orig ./suexec.c
77a78
>     "AWSTATS_FORCE_CONFIG=",

И собираем получившееся такой командой (при условии что установлены пакеты apache2-threaded-dev и build-essential):

gcc -DLOG_EXEC='"/var/log/apache2/suexec.log"' \
    -DAP_DOC_ROOT='"/var/www"' \
    -DAP_GID_MIN=100 \
    -DAP_HTTPD_USER='"www-data"' \
    -DAP_LOG_EXEC='"/var/log/apache2/suexec.log"' \
    -DAP_SAFE_PATH='"/usr/local/bin:/usr/bin:/bin"' \
    -DAP_UID_MIN=100 \
    -DAP_USERDIR_SUFFIX='"public_html"' \
    -I/usr/include/apr-1.0 \
    -I/usr/include/apache2 \
    -o suexec suexec.c && chmod 4755 suexec

В убунту надо будет заменить получившимся бинарником враппер /usr/lib/apache2/suexec.

PS: да, для того чтоб у пользователя работал awstats при suexec, который ставит ограничение на скрипты в /var/www, да еще и разыменовывает симлинки, достаточно в cgi-bin создать простейший враппер (awstats.pl):

#!/usr/bin/env perl

exec('/usr/lib/cgi-bin/awstats.pl');

Хотя можно (по идее!) в AP_DOC_ROOT прописать ‘”/var/www,/usr/lib/cgi-bin”‘.

PS2: в списке разрешенных переменных окружения можно задавать множества – например, “AWSTATS_”.

PS3: посоветовал бы кто-то настраиваемую замену для злого suEXEC – у него-то все опции при компиляции задаются.

В связи с переездом на новый сервер пришлось переносить и кучу сервисов, которые крутились на старом. Одним из них был DNS.

Настоятельно рекомендуется делать master и slave (primary и secondary) на разных машинах, дабы в случае отказа одного из них второй мог полноценно работать. Ну это в теории, на практике все несколько упрощается до одного сервера, который если ляжет – то DNS будет уже не столь важен.

Начал подымать master на OpenSuSE 11.1. Конфиг простой и незамысловатый:

#each slave should be put here (space separated) to allow zone details transfer
allow-axfr-ips=1.1.1.2
disable-axfr=no
daemon=yes
guardian=yes
default-ttl=3600
soa-refresh-default=3600
lazy-recursion=yes
local-address=0.0.0.0
master=yes
recursor=127.0.0.1:5300
launch=gmysql
gmysql-host=localhost
gmysql-port=3306
gmysql-dbname=powerdns
gmysql-user=powerdns
gmysql-password=powerdns
gmysql-socket=/var/run/mysql/mysql.sock

и рекурсор:

chroot=/var/lib/pdns
local-address=127.0.0.1
local-port=5300
setgid=pdns
setuid=pdns

Базу для него создавал из PowerDNS on Rails, хотя можно ограничиться и той, что в документации.

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

http://download.opensuse.org/repositories/server:/dns/openSUSE_11.1/

и была установлена версия pdns-2.9.22-1.1. Проблема с нагрузкой на БД полечилась, но появилась новая – с репликацией на slave.

В PowerDNS есть такое понятие как supermaster. В общем случае нужно прописывать зону на мастере и слейве. Когда слейв получит от мастера notify, он запросит у мастера записи из этой зоны. Но если сервер, от которого пришел notify, будет обнаружен в списке supermasters, и такой зоны на слейве не будет описано, она создастся автоматически. И потом в нее будут добавлены записи с мастера. Это приятно упрощает жизнь ;) В табличке запись довольно простая – ip мастера, имя слейва (ns2.example.org), имя аккаунта (на работу никак не влияет – просто будет фигурировать в описании зоны; удобно для указания клиента, например).

В порядке эксперимента попинал слейв нотификейшенами для разных доменов. Не с первого раза, но заработало:

pdns_control notify example.org

С первого раза не заработало потому, что в PowerDNS on Rails в имя поддомена по привычке вписал “@”, и потом получил записи вида “@.example.org” вместо “example.org” либо “@”. Прошелся простым апдейтом по базе и убрал этот баг.

PS: Хочу выразить благодарность Патрику Фею (Patrick Fey) за его недавний пост, который помог постичь смысл supermaster’ов.

PPS: конфиг pdns для слейва мало чем отличается от мастера:

config-dir=/etc/powerdns
daemon=yes
disable-axfr=yes
guardian=yes
launch=gmysql
lazy-recursion=yes
local-address=0.0.0.0
local-port=53
module-dir=/usr/lib/powerdns
setgid=pdns
setuid=pdns
slave=yes
socket-dir=/var/run
version-string=powerdns
gmysql-host=localhost
gmysql-port=3306
gmysql-dbname=powerdns
gmysql-user=powerdns
gmysql-password=powerdns

Переезжал на новый сервер, и наигрался вволю со всеми мониторингами и прочим. Была обнаружена неприятная особенность RRD: бд платформозависима. Пришлось на старом сервере делать дамп

for i in *.rrd; do rrdtool dump $i > $i.xml; done

тащить на новый (tar, scp либо nc) и там разворачивать:

for i in *.xml; do A=`echo $i|sed 's/\.xml//'`; rrdtool restore -f $i $A; done

По поводу того как тащить данные между серверами – ninja way:
На стороне получателя

netcat -l -p 7000 | tar x

На стороне отправителя

tar cf - * | netcat otherhost 7000

Ссылки по теме:
tarpipe
RRD convert

Столкнулся с необходимостью добавить к PHP в макоси несколько экстеншенов.
Апач в MacOS работает в x86_64. Да-да, там бинарник для нескольких архитектур:

file `which httpd`

Проблема в том что по умолчанию экстеншены будут собираться под i386. И потому из php-cli они доступны и работают, в то время как в mod_php их нет, а в логах апача видно примерно такое:

PHP Warning:  PHP Startup: Unable to load dynamic library '/usr/lib/php/extensions/no-debug-non-zts-20060613/mcrypt.so' - (null) in Unknown on line 0

Для сборки под несколько архитектур надо собирать все зависимости также под несколько архитектур. Я решил ограничиться вариантом x86_64 и i386.

Итак, в /opt/local/etc/macports/macports.conf добавим требуемые архитектуры:

sudo echo 'universal_archs x86_64 i386'>> /opt/local/etc/macports/macports.conf

Чудесно. Теперь на примере gettext.

sudo port install gettext +universal
file `which gettext`

В качестве вывода мы должны получить что-то вроде

dm@Loki ~$ file `which gettext`
/opt/local/bin/gettext: Mach-O universal binary with 2 architectures
/opt/local/bin/gettext (for architecture i386): Mach-O executable i386
/opt/local/bin/gettext (for architecture x86_64):   Mach-O 64-bit executable x86_64

Далее нужны сорсы php. В сорсах есть папка ext.

cd php-5.2.8/ext/gettext
phpize

CFLAGS="-arch i386 -arch x86_64 -g -Os -pipe -no-cpp-precomp" \
CCFLAGS="-arch i386 -arch x86_64 -g -Os -pipe" \
CXXFLAGS="-arch i386 -arch x86_64 -g -Os -pipe" \
LDFLAGS="-arch i386 -arch x86_64 -bind_at_load" \
./configure --with-gettext=/opt/local

make
sudo make install
sudo echo 'extension=gettext.so' >> /etc/php.ini
sudo apachectl restart

Предварительно убедитесь, что у Вас есть файл /etc/php.ini. По умолчанию его нет.

После этого модуль должен завестись в mod_php.

UPD: а чобы (по возможности) все из портов собиралось с флагом universal надо сделать

echo '+universal' >> /opt/local/etc/macports/variants.conf

My fail with MySQL

April 9th, 2009

Сегодня сделал глупость – поторопился. И уложил MySQL. А дело было так…

Создал таблицу test1 с хранилищем InnoDB, и начал делать в нее load data infile. Но процесс затянулся, и я решил убить его. Зря. Убить процесс в мускуле не удалось, и я злостно его перезапустил.

После перезапуска MySQL запусти фоновое восстановление таблицы, и не отзывался ни на что. Потому был потушен еще раз.

Затем вместо того чтоб прописать в my.cnf

innodb_force_recovery = 4

я остановил его и… грохнул test1.frm. Руками, на диске.

После перезапуска ничего не изменилось. И я уже запустил MySQL с указаной опцией. И тут я понял свою ошибку. А ведь надо было не удалять файл, а перезапуститься с innodb_force_recovery и сделать уже оттуда

DROP TABLE test1;

В результате поисков было найдено решение: дампить БД, убивать БД, выкатывать БД из дампа. Так tablespace в InnoDB почистится.

Вывод: не торопиться и не пороть гарячку. Доки прийдется читать в любом случае, зато не нервничая при этом.

UPD: еще большей глупостью было убить логи не дожидаясь clean shutdown. Теперь лишь mysqldump –all-databases, убить, restore…

UPD2: удалением БД дело не ограничилось. Пришлось дампить все InnoDB таблицы, затем удалять /var/lib/ib*, в том числе и ibdata (при остановленом мускуле), запускать его и вкатывать дамп обратно.

На заметку:
forcing-recovery
Recovery howto

CSS Layout

March 9th, 2009

Все время забываю название шаблона для админки сайтов – mollio.
Так что это просто заметка чтоб не искать долго в следующий раз.

Ссылки на другие лайауты приветствуются ;)