Перезапуск WEB-сервера с нулевым временем простоя
Материал из Eludia.
Содержание |
Мортен Харкет держит одну ноту в течение 20.2 с. Это очень много. Но правильно настроенный хор может держать одну ноту вообще неограниченно долго. Пока одна треть хористов поёт в полную силу, другая потихонечку затихает, а третья набирает воздух. Или восстанавливает силы каким-то иным образом.
Этот же принцип, правда, в несколько упрощённом варианте, можно применить и при настройке WEB-приложения. Возьмём на роли певцов 2 экземпляра Apache/mod_perl, а на на должность хормейстера поставим nginx.
Цель наших действий: получить конфигурацию серверов, позволяющую совершать полную перезагрузку WEB-приложения (например, при обновлении ядра Eludia.pm или других базовых perl-модулей) таким образом, чтобы ни один клиент не заметил перерыва в работе.
Приведённое описание непосредственно применимо для Debian GNU/Linux с установленными пакетами nginx и apache-perl, но конфигурация довольно просто переносится на многие другие платформы.
Правда, стоит отметить, что описанная конфигурация не позволяет использовать NTLM-авторизацию.
Шаг 1. Установить nginx как proxy
Этот момент рассмотрен отдельно, так что не будем описывать его подробно.
Шаг 2. Клонировать apache-perl
Имена
Нам понадобится 2 симметричных инсталляции Apache / mod_perl. Чтобы их различать, выберем 2 символических имени, удовлетворяющих следующим условиям:
- короткие;
- одинаковые по длине;
- C-идентификаторы;
- парные по смыслу;
- идентичние по эмоциональным ассоциациям (чтобы ни один не имел приоритета).
Здесь мы будем использовать sea и sky.
Конфигурации
Вернёмся к системному администрированию. Поэтому начнём с того, что рекурсивно скопируем /etc/apache-perl под именами /etc/apache-sea и /etc/apache-sky. Теперь подправим номер порта в одном из них.
В идеале стоит вынести все директивы, не связанные с именем (как PidFile) и номером порта, в общий Include. И, разумеется, повыкидывать все ненужные модули и опции, вроде mod_userdir и AddDefaultCharset, а заодно и комментарии. Теперь, когда весь общий конфиг уместился на одной странице, пройдитесь ещё раз по числовым параметрам и задумайтесь над каждым из них. Сколько у вас MaxClients? 150? А если помножить его на 100 Мб? А если вспомнить, что статикой занимается nginx? У вас и правда 150 одновременно исполняемых запросов? Ну и так далее.
Исполнимые файлы
Разумно использовать одни и те же бинарные файлы самого сервера и модулей; различаться будут только конфигурации. Однако, поскольку в некоторые моменты оба экземпляра будут работать одновременно, чтобы не путаться в списке процессов, удобно создать символические ссылки-синонимы:
cd /usr/sbin ln -s apache-perl apache-sky ln -s apache-perl apache-sea
Теперь — скрипты. Для запуска и останова будем применять дебиановские пускачи, а периодический configtest рассматривать не будем, так что apachectl не копируем. Зато, поскольку стандартному стартёру можно передавать только имя файла, но не параметры, нам понадобятся скрипты под названиями apache-seadaemon apache-skydaemon (в смысл не вдумываемся) с содержимым
#!/bin/bash /usr/sbin/apache-s?? -f /etc/apache-s??/httpd.conf
Далее идём в /etc/init.d и копируем apache-perl под 2 уже известными именами, после чего в каждом из экземпляров правим /usr/sbin/apache-perl на имя соответствующего демона. Ну и расставляем ссылки на эти файлы из /etc/init.d/rc*.d. Да, при перезагрузке сервера они будут стартовать оба, ничего страшного.
Проверка конфигурации:
/etc/init.d/apache-s?? start HEAD http://127.0.0.1/:????/?type=login /etc/init.d/apache-s?? stop HEAD http://127.0.0.1/:????/?type=login
Шаг 3. Переконфигурировать nginx
На 1-м шаге мы уже настроили nginx как реверсный прокси, соответственно, где-то в /etc/nginx.conf есть строка с директивой proxy_pass. Берём её и пишем в 2 вновь создаваемых файла:
/etc/nginx/sites-available/sea /etc/nginx/sites-available/sky
Перед записью проверяем номер порта: он должен соответствовать апачевскому с тем же именем. Теперь подбрасывем монетку, выбираем 1 из 2 имён и создаём символическую ссылку
cd /etc/nginx ln -s sites-available/s?? sites-enabled/s??
а в самом /etc/nginx.conf исходную строку proxy_pass заменяем на
include /etc/nginx/sites-enabled/*
Проверка конфигурации:
nginx -t
Шаг 4. И, наконец, установить скрипт перезапуска
Этот код далеко не претендует на оптимальность, однако вполне работоспособен:
#!/usr/bin/perl -w
use LockFile::Simple qw(trylock unlock);
my $LOCKFILE = '/var/run/restart_sea_sky';
my $NGINX_DIR = '/etc/nginx';
my $NGINX_SITES = "$NGINX_DIR/sites-enabled";
my $NGINX_CONF = "$NGINX_DIR/sites-available";
my $TRIES = 5;
################################################################################
sub _log ($) {
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
printf STDERR "%04d-%02d-%02d %02d:%02d:%02d [%5d] %s\n", $year + 1900, $mon +1, $mday, $hour, $min, $sec, $$, $_[0];
}
################################################################################
_log 'Restarting web servers...';
unless (trylock ($LOCKFILE)) {
_log 'Lock file detected. I quit.';
exit;
}
my $old = `ls $NGINX_SITES`;
chomp $old;
_log "Now running '$old'...";
my $new;
if ($old eq 'sea') {
$new = 'sky';
}
elsif ($old eq 'sky') {
$new = 'sea';
}
else {
unlock ($LOCKFILE);
_log "UNKNOWN CONFIGURATION DETECTED!";
die;
}
_log "Starting up '$new'...";
`/etc/init.d/apache-$new start`;
my $conf = `cat $NGINX_CONF/$new`;
$conf =~ /proxy_pass\s+(.*);/;
my $url = $1;
my $up = 0;
for my $i (1 .. $TRIES) {
_log "Checking '$url', try $i...";
my $head = `HEAD ${url}/?type=logon`;
if ($head =~ /^200 OK/ && $head =~ /X-Powered-By\: Eludia/) {
_log "It's up and running, very good...";
last;
}
if ($i == $TRIES) {
_log "No chance. $new IS NOT STARTED! The last response head was:\n$head";
unlock ($LOCKFILE);
exit;
}
sleep (1);
}
_log "Switching nginx configuration...";
`rm $NGINX_SITES/$old; ln -s $NGINX_CONF/$new $NGINX_SITES/$new`;
_log "Reloading nginx...";
`/etc/init.d/nginx reload`;
_log "Shutting down $old...";
`/etc/init.d/apache-$old stop`;
unlock ($LOCKFILE);
_log 'Over and out.';
