воскресенье, 7 сентября 2014 г.

Тайловый сервер на основе Python, Mapnik и Bottle

В прошлом я писал серию статей о настройке тайлового сервера на основе apache2, renderd и модуля mod_tile. Недостаток этой связки, с моей точки зрения, заключается во-первых в необходимости использования apache, а во-вторых в том, что renderd и mod_tile отсутствуют в репозиториях Debian. Из-за второго недостатка нет уверенности в том, что при очередном обновлении её удастся успешно собрать и продолжить использовать.

Несколько месяцев назад мне попалась статья Дениса Рыкова Основы работы динамических TMS-сервисов, в которой приводится пример тайлового сервера, написанного на Python с использованием веб-фреймворка Bottle и библиотеки Mapnik. Статья привлекла моё внимание ещё потому, что в последних проектах я начал пользоваться фреймворком Bottle. Этот фреймворк обладает минимальным количеством зависимостей и может всё, что мне нужно от веб-фреймворков (да, мои потребности не велики), не навязывая при этом собственный стиль разработки, как это происходит, например, с Django.

Я решил воспользоваться этой статьёй для того, чтобы избавить себя от возможных проблем с используемой мной связкой на основе apache2, renderd и mod_tile.

1. Тайловый сервер

Для начала установим библиотеку Mapnik и веб-фреймворк Bottle:
# apt-get install python-mapnik2 python-bottle
Сам тайловый сервер разместим в файле /usr/local/share/tiles/main.py:
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from bottle import get, response, run, debug, app, HTTPError
import mapnik2 as mapnik
import os

from settings import TILE_WIDTH, TILE_HEIGHT, BBOX_MINX, BBOX_MINY, BBOX_MAXX, BBOX_MAXY, MAPNIK_CONFIGS, CACHE_PATH

# Сохранение тайла в файловую систему в целях кэширования
def save_tile(filename, body):
    path, name = os.path.split(filename)
    try:
        os.makedirs(path)
    except:
        pass
    file = open(filename, 'w')
    file.write(body)
    file.close() 
 
@get('/<service>/1.0.0/<layer>/<z:int>/<x:int>/<y:int>.png')
def tms(service, layer, z, x, y):
    # Считаем охват тайла на выбранном масштабном уровне
    stepx = (BBOX_MAXX - 1.0 - BBOX_MINX) / (2 ** z)
    stepy = (BBOX_MAXY - 1.0 - BBOX_MINY) / (2 ** z)

    # Выбираем охват тайла из словаря extents согласно типу запрашиваемого сервиса (tms или xyz)
    if service == 'tms':
        box = mapnik.Box2d(BBOX_MINX + x * stepx,
                           BBOX_MINY + y * stepy,
                           BBOX_MINX + (x + 1) * stepx,
                           BBOX_MINY + (y + 1) * stepy)
    elif service == 'xyz':
        box = mapnik.Box2d(BBOX_MINX + x * stepx,
                           BBOX_MAXY - (y + 1) * stepy,
                           BBOX_MINX + (x + 1) * stepx,
                           BBOX_MAXY - y * stepy)
 
    # Указываем путь, где находится файл с настройками Mapnik
    map = mapnik.Map(TILE_WIDTH, TILE_HEIGHT)
    mapnik.load_map(map, os.path.join(MAPNIK_CONFIGS, layer + '.xml'))
 
    # Нацеливаем карту на выбранный охват
    map.zoom_to_box(box)
 
    # Отрисовываем тайл
    image = mapnik.Image(map.width, map.height)
    mapnik.render(map, image)
    body = image.tostring('png')

    # Сохраняем тайл в файловой системе
    filename = os.path.join(CACHE_PATH, service, '1.0.0', layer, str(z), str(x), str(y) + '.png')
    save_tile(filename, body)

    # Передаём ответ клиенту
    response.content_type = 'image/png'
    return body

if __name__ == '__main__':
    debug(True)
    run(host = '0.0.0.0', port = 8080)
else:
    application = app()
В отдельном файле /usr/local/share/tiles/settings.py разместим настройки этого приложения:
#!/usr/bin/python
# -*- coding: UTF-8 -*-

# Размеры тайла
TILE_WIDTH = 256
TILE_HEIGHT = 256

# Размеры карты
BBOX_MINX = -20037508.0
BBOX_MINY = -20037508.0
BBOX_MAXX = 20037508.0
BBOX_MAXY = 20037508.0

# Каталог с xml-файлами с описаниями слоёв
MAPNIK_CONFIGS = '/etc/mapnik-osm-data/'
# Каталог для хранения кэша тайлов
CACHE_PATH = '/var/cache/tiles'
Таким образом, в запросе можно указывать имя слоя, тайл из которого нужно вернуть. Имя слоя преобразуется в имя XML-файла, из которого извлекается его описание. Например, слою OpenStreetMap будет соответствовать запрос http://tiles.domain.tld/xyz/1.0.0/osm/0/0/0.png, при обслуживании которого будет использован файл /etc/mapnik-osm-data/osm.xml.

Создадим каталог для кэша тайлов:
# mkdir /var/cache/tiles
Проставим права доступа к нему для веб-сервера:
# chown www-data:www-data /var/cache/tiles
2. Сервер приложений uwsgi

Установим uwsgi и плагин к нему uwsgi-plugin-python, если они ещё не установлены:
# apt-get install uwsgi uwsgi-plugin-python
Создадим файл конфигурации /etc/uwsgi/apps-available/tiles.ini:
[uwsgi]

procname = uwsgi-tiles
procname-master = uwsgi-tiles-master

chdir = /usr/local/share/tiles
#module = app:application
mount = /=main:application
plugin = python27
master = true
processes = 8
Добавим файл конфигурации в каталог включенных приложений:
# cd /etc/uwsgi/apps-enabled
# ln -s /etc/uwsgi/apps-available/tiles.ini .
Запустим новое приложение:
# /etc/init.d/uwsgi start tiles
3. Веб-сервер nginx

Установим nginx, если он ещё не установлен:
# apt-get install nginx-full
Для доступа к файлам в кэше и запросе файла у тайлового сервера при их отсутствии в кэше создадим файл конфигурации. Например, если этот веб-сервер будет использоваться только для отрисовки тайлов, можно создать файл /etc/nginx/sites-available/default со следующим содержимым:
server {
  listen 0.0.0.0:80;
#  server_name tiles.domain.tld;

  root /var/www;

  location @tiles {
    uwsgi_pass unix:/var/run/uwsgi/app/tiles/socket;
    include uwsgi_params;
  }

  location /tms/ {
    root /var/cache/tiles/;
    try_files $uri @tiles;
  }

  location /xyz/ {
    root /var/cache/tiles/;
    try_files $uri @tiles;
  }
}
Включим использование этого файла веб-сервером:
# cd /etc/nginx/sites-enabled
# ln -s /etc/nginx/sites-available/tiles .
Перезапустим nginx, чтобы его новые настройки вступили в силу:
# /etc/init.d/nginx restart
4. Очистка кэша

Осталось только настроить периодическую чистку кэша от тайлов, к которым давно не было обращений. Это позволит достичь более высокой эффективности кэша в пересчёте на его объём. Сделать это просто - можно воспользоваться обычыми для Unix инструментами - планировщиком задач cron и командой find. Добавим в файл /etc/crontab такую строчку:
30    8 * * *   www-data     find /var/cache/tiles -type f -atime 14 -delete ; find /var/cache/tiles -type d -empty -delete
Из каталога кэша тайлов будут удаляться файлы, к которым не обращались более 14 дней, а также пустые каталоги.

Хочу отметить, что рассматриваемый тайловый сервер - скорее заготовка, т.к. он, например, практически не осуществляет обработку возможных исключительных ситуаций - отсутствие файла конфигурации, отсутсвие прав доступа к каталогу тайлов и т.п. Такие исключительные ситуации в конечном итоге попадают в фреймворк Bottle, который при их возникновении просто сообщает о внутренней ошибке сервера с кодом 500.

воскресенье, 24 августа 2014 г.

Настройка nginx, php5-fpm и uwsgi

До недавнего времени я долгое время пользовался веб-сервером Lighttpd, который меня во всём устраивал. Но сейчас я стал пользоваться nginx, хотя сам nginx на такой выбор непосредственно не повлиял. Но, обо всём по порядку.

1. Менеджер процессов php5-fpm

В начале года я заинтересовался менеджером процессов php5-fpm для обслуживания приложений на PHP. Он обладает большим количеством приятных преимуществ, основанных на так называемых пулах. Пул - это группа процессов, выделенная для обработки запросов, поступающих на определённый порт или Unix-сокет. Для каждого пула действует своя политика управления процессами:
  • static - строго постоянное количество процессов-обработчиков,
  • dynamic - переменное количество обработчиков, для которых указывается минимальное и максимальное количество процессов, а также количество процессов-обработчиков "на подхвате", которые держатся готовыми на случай внезапного наплыва нагрузки, чтобы не терять время на порождение новых процессов-обработчиков,
  • ondemand - режим, при котором обработчики порождаются только при поступлении запросов и завершаются спустя указанный период простоя.
Каждый пул может быть запущен от имени отдельного пользователя, так что можно легко изолировать несколько приложений друг от друга. Кроме того, даже если пулы работают от имени одного и того же пользователя, разделение приложений по пулам позволяет предотвратить ситуацию, когда высоконагруженное приложение постоянно держит занятыми процессы-обработчики, не давая таким образом нормально работать лёгким интерактивным приложениям.

Ну и самое приятное заключается в том, что для каждого из пулов можно задавать разные настройки PHP или вообще строго фиксировать их, запрещая приложению менять эти настройки самостоятельно. Например, какое-то приложение может требовать для работы много памяти (веб-интерфейс Zabbix, например), другое может долго формировать страницу (например, какое-то веб-приложение, формирующее отчёт), третьему нужно ограничить таймауты подключения к базе данных или время ожидания данных из сокета. В таком случае каждому приложению можно выделить отдельный пул и задать отдельные настройки.

Благодаря своим особенностям php5-fpm очень хорошо подходит для виртуального хостинга, когда на одном сервере может обслуживаться множество приложений разных пользователей. Думаю, что это уже понятно.

Однако, php5-fpm можно хорошо использовать и совместно с Lighttpd. Lighttpd даже в чём-то проще. Например, при совместной работе php5-fpm и Lighttpd мне не приходилось настраивать размеры буферов для чтения заголовков ответов, что может понадобиться сделать при использовании nginx и какого-то приложения, устанавливающего большое количество cookie. Если вам требуется запускать только приложения, написанные на PHP, то связка Lighttpd и php5-fpm может оказаться оптимальным выбором, особенно если вы уже знакомы с Lighttpd.

2. Сервер uwsgi

Позже мне потребовалось запускать веб-приложения, написанные на веб-фреймворках на языках Python и Perl. В этих веб-фреймворках приложение в конечном итоге представляет собой объект, поддерживающий интерфейсы WSGI или PSGI соответственно. Я уже пользовался веб-серверами, которые реализуют HTTP-интерфейс для приложений WSGI и PSGI - это были flup и starman. Оба веб-сервера мне не понравились своей требовательностью к ресурсам, поскольку они написаны на соответствующих интерпретируемых языках, а также необходимостью под каждое веб-приложение создавать отдельный скрипт инициализации. Этих проблем был лишён найденный сервер uwsgi, который написан на Си, имеет удобный сценарий инициализации и позволяет запускать веб-приложения с интерфейсом WSGI и PSGI. С его помощью можно даже запускать приложения Rack, написанные на Ruby. Один недостаток - снаружи он предоставляет не HTTP-интерфейс, а всё тот же WSGI.

Совместно с uwsgi умеет работать nginx, так что выбор такой связки был предопределён. Осталось решить последний вопрос - как запускать классические CGI-приложения? Поначалу я думал воспользоваться сервером spawn-fcgi или fcgiwrap, но потом нашёл CGI-плагин для uwsgi, так что для запуска любого веб-приложения оказалось достаточно лишь php5-fpm и uwsgi, которыми ещё и очень удобно управлять. Несмотря на то, что в uwsgi имеется возможность обслуживать запросы к приложениям на PHP, я всё же предпочёл использовать для PHP сервер php5-fpm, обладающий большим количеством настроек, позволяющих получить больший контроль над PHP-приложениями.

Именно таким образом я и пришёл к решению сменить привычный мне веб-сервер Lighttpd на связку nginx + php5-fpm + uwsgi. В 2008 я перешёл от использования Apache на Lighttpd, в 2014 я сменил Lighttpd на nginx. С тех пор понимание того, каким должен быть хороший веб-сервер кардинально изменилось. Если раньше это был комбайн, в котором было довольно тяжело ориентироваться, то теперь это несколько взаимодействующих программ, каждая из которых очень наглядна, быстро осваивается, радует удобством и скоростью работы.

3. Установка пакетов

Если вы планируете использовать nginx только совместно с php5-fpm, то установить потребуется только два пакета - легкий nginx и сам менеджер процессов:
# apt-get install nginx-lite php5-fpm
Если же вы планируете использовать uwsgi, то вам нужна полная версия nginx и uwsgi-сервер:
# apt-get install nginx-full uwsgi
Дополнительно, в зависимости от того, какие приложения вы собираетесь запускать под управлением uwsgi, вам может понадобиться один или несколько дополнительных пакетов. Для поддержки выполнения CGI-скриптов при помощи uwsgi установите пакет с соответствующим модулем:
# apt-get install uwsgi-plugin-cgi
Если вы планируете запускать Perl-приложения с интерфейсом PSGI, то вам понадобится модуль для поддержки соответствующего протокола:
# apt-get install uwsgi-plugin-psgi
Наконец, uwsgi можно использовать и для запуска WSGI-приложений, написанных на Python. Для этого нужен модуль, называющийся... python:
# apt-get install uwsgi-plugin-python
4. Заготовка файла конфигурации nginx

Создадим заготовку файла конфигурации /etc/nginx/sites-enabled/default:
server {
  listen 0.0.0.0:80;
  # server_name info.domain.tld;

  root /var/www;
  index index.html index.php index.pl;
}
В последующих разделах приводятся небольшие фрагменты, которые можно добавить внутрь секции server этой заготовки.

Здесь настраивается виртуальный сервер, принимающий подключения на TCP-порту 80 на любой настроенный на компьютере IP-адрес. Если необходимо выделить несколько виртуальных веб-серверов, работающих на разных IP-адресах, можно создать несколько секций, указав в каждой из них соответствующий IP-адрес.

Если необходимо запустить на одном IP-адресе виртуальные серверы, отличающиеся доменным именем, то можно раскомментировать опцию server_name и указать одно или несколько доменных имён через пробел. Один из таких серверов можно отметить как сервер по умолчанию при помощи ключевого слова default в опции listen в самом её конце - на него будут поступать запросы, для которых не удалось найти секцию с подходящим доменным именем, но поступившие на этот IP-адрес.

Опция root задаёт каталог, из которого будут браться запрошенные файлы. Если имя конкретного файла не указано, а указан лишь каталог, то будут перебираться по очереди имена, указанные в опции index. Первый же существующий файл будет отдан клиенту или обработан как приложение (в зависимости от настроек из последующих секций).

5. CGI-приложения на PHP

Для выполнения приложений на PHP воспользуемся уже упомянутым сервером php-fpm. Создадим файл с описанием пула, который будет обрабатывать запросы к PHP-файлам. Для этого создадим файл /etc/php5/fpm/pool.d/default.conf или приведём имеющийся файл к следующему виду:
; Имя пула
[default]

; Рабочие процессы пула будут работать от имени указанного пользователя и группы
user = www-data
group = www-data

; Пул будет ожидать запросы на указанном Unix-сокете
listen = /var/run/php.sock

; Владелец Unix-сокета, его группа и права доступа к сокету
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Динамический менеджер рабочих процессов будет поддерживать от 10 до 30 процессов,
; из которых от 5 до 10 могут простаивать в ожидании поступления новых запросов.
; Если простаивающих процессов будет меньше 5 - будут порождены новые процессы,
; если простаивающих процессов окажется больше 10 - лишние будут завершены
pm = dynamic
pm.max_children = 30
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 10

; Тут можно ограничить количество запросов, последовательно обслуживаемых одним процессом
; После этого процесс будет завершён и запущен снова - это может помочь от утечек памяти
;pm.max_requests = 500

; Если обработка одного запроса длится дольше трёх минут - обработка запроса принудительно завершается
request_terminate_timeout = 180s

; В этом файле можно вести журнал обработанных запросов
;access.log = /var/log/php5-fpm.access.log

; Тут можно задать настройки, которые обычно указывают в файле php.ini
; Разница в том, что эти настройки будут действовать не глобально, а только внутри пула
; Настройки php_admin нельзя поменять изнутри самого PHP-приложения
php_value[data.timezone] = Asia/Yekaterinburg
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php5-fpm.errors.log
;php_admin_value[memory_limit] = 128M
php_admin_value[mysql.connect_timeout] = 1
php5-fpm умеет перечитывать файлы конфигурации, соответствующим образом меняя состав пулов и их настройки, не трогая те пулы, настройки которых не поменялись:
# /etc/init.d/php5-fpm reload
Однако, если вам кажется, что настройки не вступили в силу, можно перезапустить php5-fpm целиком, чтобы он гарантированно прочитал и применил новые настройки пулов, полностью перезапустив их процессы:
# /etc/init.d/php5-fpm restart
Теперь нужно добавить в заготовку файл, например, со следующим содержимым:
location ~ ^/php/(base|index|logout|query)\.php$ {
  fastcgi_pass unix:/var/run/php.sock;
  include fastcgi_params;
}

location /php/static/ {
  alias /usr/local/share/php/static/;
}
В первой секции location перечисляются файлы, запросы к которым будут обрабатываться созданным нами пулом процессов php5-fpm. Во второй секции location указан путь к статическим файлам, отдачей которых будет заниматься сам nginx.

Стоит отметить, что секции без регулярных выражений имеют приоритет над секциями с регулярными выражениями. Первые совпадают с каталогом и всеми подкаталогами и файлами внутри него, вторые - с файлами и каталогами, удовлетворяющими регулярным выражениям. Самым высоким приоритетом обладают секции со знаком =, которые указывают точное соответствие с файлом или каталогом, без подкаталогов.

Осталось перезапустить nginx, чтобы его новые настройки вступили в силу:
# /etc/init.d/nginx restart
nginx тоже умеет перезагружать обновлённую конфигурацию, однако рабочие процессы могут некоторое время продолжать работать со старой конфигурацией, пока не будут вытеснены новыми. Если это не критично, то перезагрузить его можно так:
# /etc/init.d/nginx reload
6. PHP-приложения на фреймворке CodeIgniter

Так получилось, что мой коллега на работе в своих приложениях на PHP использует веб-фреймворк CodeIgniter, который несколько необычно для PHP обрабатывает запросы. Все запросы обрабатываются одной страницей, которая уже сама учитывает таблицу маршрутов или непосредственно вызывает необходимые методы из классов контроллеров.

Прежде чем приступить к настройке nginx, сначала настроим само приложение - в файле application/config/config.php нужно прописать следующие настройки:
$config['base_url'] = '';
$config['index_page'] = '';
$config['uri_protocol'] = 'REQUEST_URI';
Первые две настройки не заданы, т.к. будет использоваться автоматическое их определение самим фреймворком.

Теперь пришла очередь nginx. Для обработки запросов к приложению будем использовать пул php5-fpm, настройка которого была описана в предыдущем разделе. В нашу заготовку файла конфигурации nginx /etc/nginx/sites-enabled/default.conf добавим следующие настройки:
location /ci/ {
  fastcgi_pass unix:/var/run/php.sock;
  include fastcgi_params;

  fastcgi_buffer_size 64k;
  fastcgi_buffers 8 64k;

  fastcgi_param SCRIPT_FILENAME /usr/local/share/ci/index.php;
  fastcgi_param SCRIPT_NAME /ci/index.php;

  fastcgi_read_timeout 120s;
}

location /ci/data/ {
  alias /usr/local/share/ci/data/;
}
В первой секции переопределяются некоторые настройки, значения по умолчанию которых нас не устраивают. Важно переопределять их после включения файла с настройками по умолчанию - иначе он сам переопределит заданные нами настройки.

Размеры буферов переопределены, поскольку CodeIgniter интенсивно использует cookie - заголовок запроса или ответа может получиться очень большим и не поместиться в буфер. В этом случае приложение может иногда работать, а иногда - не работать, в зависимости от того, какой объём имеют cookie.

Следующие опции указывают, какой файл будет обрабатывать запросы и помогут фреймворку автоматически определить значения опций base_url и index_page.

Наконец, последняя настройка указывает nginx'у, что ждать ответа от php5-fpm не стоит больше двух минут. В пуле php5-fpm определён таймаут в 3 минуты, так что в данном случае nginx отключится от php5-fpm первым, не дождавшись срабатывания таймаута в процессе-обработчике.

Не забудьте перезагрузить или перезапустить nginx:
# /etc/init.d/nginx restart
7. CGI-приложения на Perl

Запросы к CGI-приложениям может обслуживать uwsgi с модулем uwsgi-plugin-cgi. В uwsgi нет понятия пулов и нет единственного мастер-процесса, который бы шефствовал над остальными процессами. Вместо этого можно определить несколько раздельных файлов конфигурации, у каждого из которых будет собственный мастер-процесс. Централизованное управление мастер-процессами на данный момент осуществляется при помощи единого init-скрипта.

В новых версиях uwsgi появился режим emperor, который работает полностью аналогично php5-fpm - с одним мастер-процессом. Более того - он умеет обнаруживать появление новых файлов приложений или изменения в имеющихся файлах, поддерживая состав и настройки рабочих процессов актуальными. Однако, в Debian Wheezy эта новая версия uwsgi ещё не попала, поэтому я опишу текущее положение дел.

Определим настройки приложения в файле /etc/uwsgi/apps-enabled/cgi.ini:
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-cgi
procname-master = uwsgi-cgi-master

; Используемый плагин и его настройки: корень с файлами, обрабатываемые расширения,
; максимальное время обработки запроса скриптом
plugins = cgi
cgi = /var/www
cgi-allowed-ext = .pl
cgi-timeout = 120

; Рабочих процессов будет четыре - можно будет одновременно обслуживать не более 4 запросов,
; а последующие запросы будут вставать в очередь до освобождения одного из рабочих процессов
processes = 4
Теперь можно перезапустить все приложения или индивидуально - только что созданное:
# /etc/init.d/uwsgi restart
# /etc/init.d/uwsgi start cgi
После запуска нового приложения uwsgi создаст Unix-сокет /var/run/uwsgi/app/cgi/socket, где cgi - имя приложения. Кроме того, будет создан журнал работы приложения в файле /var/log/uwsgi/app/cgi.log, где cgi - опять то же имя приложения.

Задаём настройки nginx, вписав в заготовку файла конфигурации ещё один фрагмент:
location = /pl/index.pl {
  uwsgi_pass unix:/var/run/uwsgi/app/cgi/socket;
  include uwsgi_params;

  # Модификатор для протокола CGI
  uwsgi_modifier1 9;
  # Скрипт, обрабатывающий запросы
  uwsgi_param SCRIPT_FILENAME /usr/local/share/pl/index.pl;
}

location /pl/static/ {
  alias /usr/local/share/pl/static/;
}
Значение опции uwsgi_modifier1 берётся из таблицы на странице The uwsgi Protocol. В данном случае используется протокол CGI, значение модификатора - 9.

Не забудьте перезагрузить или перезапустить nginx.

8. Perl с фреймворком Dancer

Для запуска PSGI-приложений под управлением uwsgi нужен файл, возвращающий объект PSGI. Для этого создадим файл приложения /usr/local/share/dancer/bin/app.psgi:
#!/usr/bin/perl

use Dancer;

...

setting apphandler => 'PSGI';

my $app = sub {
    my $env = shift;
    my $request = Dancer::Request->new(env => $env);
    Dancer->dance($request);
};
На месте многоточия располагается тело приложения, где определяются обработчики страниц.

Теперь создадим файл конфигурации веб-приложения /etc/uwsgi/apps-enabled/dancer.ini со следующим содержимым:
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-dancer
procname-master = uwsgi-dancer-master

; Настраиваем плагин и задаём количество рабочих процессов
chdir = /usr/local/share/dancer
plugin = psgi
psgi = /usr/local/share/dancer/bin/app.psgi
; Приложение используется редко - запустим два рабочих процесса
processes = 2
Запустим новое веб-приложение:
# /etc/init.d/uwsgi start dancer
В заготовку конфигурации nginx добавим следующий фрагмент:
location ~ ^/dancer/ {
  uwsgi_pass unix:/var/run/uwsgi/app/dancer/socket;
  include uwsgi_params;
  
  # Указываем модификатор для использования протокола PSGI
  uwsgi_modifier1 5;
}

location /dancer/static/ {
  alias /usr/local/share/dancer/static/;
}
Осталось перезапустить или перезагрузить nginx.

9. Python и приложение на Django в подкаталоге

При создании проекта Django генерируется файл wsgi.py, внутри которого создаётся WSGI-объект application. Для запуска Django-приложения через uwsgi создадим файл /etc/uwsgi/apps-enabled/dj1.ini с конфигурацией приложения:
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-dj1
procname-master = uwsgi-dj1-master

; В пакете на самом деле два плагина - python26 и python27, выбираем плагин python27
plugin = python27
; Каталог проекта Django
chdir = /usr/local/share/dj1
; Используем файл wsgi.py, в котором определяется WSGI-приложение application
module = wsgi:application
; Запустим шесть рабочих процессов
processes = 6
Запустим рабочие процессы приложения:
# /etc/init.d/uwsgi start dj1
А в заготовку конфигурации nginx добавим такой фрагмент:
location /dj1/ {
  uwsgi_pass unix:/var/run/uwsgi/app/dj1/socket;
  include uwsgi_params;

  # Этот модификатор используется для протокола WSGI
  uwsgi_modifier1 30;
  # Имя скрипта будет вырезаться из URL перед маршрутизацией запроса
  uwsgi_param SCRIPT_NAME /dj1;
}

location /dj1/static/ {
  alias /usr/local/share/dj1/static/;
}
Осталось перезагрузить или перезапустить nginx.

10. Python и приложение на Django в корне

Создадим файл /etc/uwsgi/apps-enabled/dj2.ini с конфигурацией приложения (здесь всё аналогично предыдущему случаю):
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-dj2
procname-master = uwsgi-dj2-master

; В пакете на самом деле два плагина - python26 и python27, выбираем плагин python27
plugin = python27
; Каталог проекта Django
chdir = /usr/local/share/dj2
; Используем файл wsgi.py, в котором определяется WSGI-приложение application
module = wsgi:application
; Запустим шесть рабочих процессов
processes = 6
Запустим рабочие процессы приложения:
# /etc/init.d/uwsgi start dj2
В заготовку конфигурации nginx добавляем фрагмент:
# Здесь нужно перечислить все корневые страницы или подкаталоги со страницами приложения
location ~ ^/(admin|login|logout|app)(/|$) {
  uwsgi_pass unix:/var/run/uwsgi/app/dj2/socket;
  include uwsgi_params;
}

# Каталог со статическими файлами
location /static/ {
  alias /usr/local/share/dj2/static/;
}

# Каталог со статическими файлами для админ-панели Django
location /static/admin/ {
  alias /usr/lib/python2.7/dist-packages/django/contrib/admin/static/admin/;
}
Особенность в том, что модификатор в данном случае не используется, а в первой директиве location нужно перечислить все страницы, обработку которых нужно передавать в Django-приложение. Впрочем, если кроме этого приложения на сервере больше ничего нет и не будет, можно создать секцию для обработки корневого каталога.

Осталось перезагрузить или перезапустить nginx.

11. Python и веб-фреймворк Bottle

В случае с фреймворком Bottle всё аналогично Django. Разница лишь в том, что в Bottle нет генератора проекта, а потому создать объект WSGI-приложения придётся самостоятельно. Если же все маршруты вашего проекта определены в приложении по умолчанию, то достаточно в начало головного файла добавить импорт функции app из модуля фреймворка:
from bottle import app
И затем использовать эту функцию для создания экземпляра WSGI-объекта приложения. Для этого создадим файл /etc/uwsgi/apps-enabled/bottle.ini:
[uwsgi]

; Имена рабочих процессов приложения и мастер-процесса
procname = uwsgi-bottle
procname-master = uwsgi-bottle-master

; В пакете на самом деле два плагина - python26 и python27, выбираем плагин python27
plugin = python27
; Каталог приложения
chdir = /usr/local/share/bottle
; Используем файл main.py, в котором есть функция app, возвращающая экземпляр WSGI-приложения
module = main:app()
; Задаём количество рабочих процессов
processes = 2
Запустим рабочие процессы приложения:
# /etc/init.d/uwsgi start bottle
nginx настраивается аналогично одному из примеров для приложений Django, в зависимости от того, будет ли приложение доступно из каталога или из корня URL.

12. О безопасности

Не будьте беспечны - не помещайте ваши приложения в корневом каталоге веб-сервера и его подкаталогах! Потенциальный злоумышленник может догадаться об этом и скачать файлы с исходными текстами ваших приложений. Даже если он не найдёт там паролей для доступа, например, к базам данных или другим серверам, он может проанализировать код приложения и найти в нём уязвимости.

воскресенье, 15 июня 2014 г.

Настройка WiFi

До сих пор у меня совершенно отсутствовал опыт работы с беспроводными сетями. Однако, всё оказалось не столь трудно, как могло бы показаться.

Для организации домашней беспроводной сети я выбрал беспроводной адаптер TP-Link TL-WN781ND с разъёмом PCI-E. Адаптер собран на базе чипа AR9485 от Atheros, которые стали поддерживаться в Linux одними из первых.

Немного теории

Когда оборудование было выбрано, настало время немного узнать об основах WiFi. WiFi - это торговая марка, обозначающая большую группу стандартов IEEE 802.11. Среди них можно выделить несколько отдельных стандартов, которые обозначаются буквенными индексами:
  • a - скорость до 54 Мбит/с, частоты 5150-5350 МГц, каналы 34-64, модуляция DSSS,
  • b - скорость до 11 Мбит/с, частоты 2400-2483,5 МГц, каналы 1-13, модуляция OFDM,
  • g - скорость до 54 Мбит/с, частоты 2400-2483,5 МГц, каналы 1-13, модуляция DSSS/OFDM,
  • n - скорость от 150 до 600 Мбит/с, в зависимости от количества антенн (от 1 до 4). Базируется на стандартах a, b и g.
Отдельно я поинтересовался вопросами безопасности. Существуют следующие алгоритмы обеспечения безопасности:
  • WEP - слабая стойкость алгоритма, может быть легко взломан,
  • WPA - более сильный алгоритм, работающий на старом оборудовании. Для внедрения этого алгоритма было достаточно обновления прошивки оборудования, поддерживающего WEP. Использует алгоритм шифрования TKIP. В настоящее время тоже легко поддаётся взлому,
  • WPA2 - новый алгоритм, для использования которого требуется специальная аппаратная поддержка, которая имеется в современном оборудовании. Использует алгоритм шифрования CCMP на основе блочного алгоритма шифрования AES. Рекомендуется использовать именно этот алгоритм.
Далее, существует два протокола аутентификации:
  • WPA-PSK/WPA2-PSK - используется общий для клиента и точки доступа ключ. Пригоден для применения в домашней сети, т.к. не требует настройки дополнительных серверов,
  • WPA-EAP/WPA2-EAP - используется протокол EAP, который, как правило, работает совместно с сервером RADIUS, осуществляющим аутентификацию, авторизацию и учёт сеансов. Этот протокол имеет множество вариантов аутентификации и используется, как правило, в больших корпоративных сетях.
Итак, если принять во внимание всё описанное, то в моём случае стоит использовать режим n (или хотя-бы g) и протокол WPA2-PSK.

Настройка точки доступа

Для настройки точки доступа воспользуемся пакетом hostapd. Установим его:
# apt-get install hostapd
В пакете поставляется пример файла конфигурации с подробными комментариями: /usr/share/doc/hostapd/examples/hostapd.conf.gz

Создадим файл конфигурации /etc/hostapd/hostapd.conf:
interface=wlan0            # Настраиваемый беспроводной интерфейс
bridge=br0                 # Включить беспроводной интерфейс в указанный интерфейс-мост

driver=nl80211             # Используемый драйвер
country_code=RU            # Код нашей страны
ieee80211d=1               # Включаем использование ограничений каналов и мощности сигнала,
                           # принятые в нашей стране
ieee80211n=1               # Включить режим n в дополнение к основному

ssid=stupin.su             # Идентификатор сети
hw_mode=g                  # Основной режим g
channel=1                  # Канал 1 (можно поэкспериментировать, задавая значения от 1 до 11)

auth_algs=1                # Использовать только алгоритмы аутентификации WPA2

wpa=2                      # Использовать только WPA2
wpa_key_mgmt=WPA-PSK       # Используем общий ключ
wpa_passphrase=passphrase  # Кодовая фраза, используемая в качестве общего ключа
wpa_pairwise=CCMP          # Используемый алгоритм шифрования
Настройку bridge можно не указывать, если вы не хотите объединять беспроводной интерфейс с Ethernet-интерфейсом в мостовой интерфейс. Я это сделал, поскольку у меня имеются проводные устройства, которые я хочу объединить в одну сеть с беспроводными.

Идентификатор сети ssid - это произвольный текст, по которому вы будете отделять свою точку доступа от чужих.

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

Вместо настройки wpa_passphrase можно использовать настройку wpa_psk, в которой указывается шестнадцатеричная последовательность, представляющая собой общий ключ, или настройку wpa_psk_file, в которой можно указывать разные ключи для разных устройств, сопоставляя их MAC-адресам устройств. Подробнее об этом можно почитать в примере файла конфигурации, идущего в составе пакета hostapd.

Я выбрал настройку wpa_passphrase, потому что большинство используемых мной устройств не позволяют указывать произвольную шестнадцатеричную последовательность, зато позволяют указывать кодовую фразу. Поскольку взломать сеть WPA2-PSK можно только последовательным перебором паролей, то лучше выбрать достаточно длинный и сложный пароль. Допускается использование до 63 символов, однако я ограничился шестнадцатью, сгенерировав их при помощи программы pwgen.

Отберём у пользователей системы возможность заглядывать в файл конфигурации, чтобы они не подсмотрели пароль (просто пятиминутка паранойи, вы можете оставить файл доступным на чтение всем):
# chmod o= /etc/hostapd/hostapd.conf
Теперь можно перезапустить hostapd, чтобы он начал анонсировать идентификатор сети и клиенты смогли подключиться к сети:
# /etc/init.d/hostapd start
К слову о паранойе. Можно запретить анонсировать точке доступа идентификатор сети, так что к сети можно будет подключиться только зная её идентификатор. Однако, в процессе подключения идентификатор сети всё равно будет передан точке доступа, так что злоумышленник сможет его перехватить. Я не запрещаю анонсы идентификатора сети, т.к. существенно безопасность при этом не увеличится, а отлаживать неполадки станет сложнее, т.к. без анонса сложно будет определить - а работает ли вообще точка доступа?

Настройка клиента

Для полного комплекта рассмотрим ещё настройку клиентского беспроводного соединения. Для этого воспользуюсь ноутбуком со встроенной WiFi-картой на основе того же чипа AR9485. Для начала установим пакет wpasupplicant:
# apt-get install wpasupplicant
В Debian настройка беспроводных соединений, как и многих других, органично вписывается в общую систему конфигурирования сети. Отдельно настраивать wpa_supplicant не требуется, т.к. все необходимые настройки можно вписать в файл /etc/network/interfaces:
allow-hotplug wlan0
iface wlan0 inet dhcp       # Настройки сети получим от DHCP-сервера
  wpa-ssid stupin.su        # Идентификатор сети
  wpa-ap-scan 1             # Идентификатор сети анонсируется точкой доступа
  wpa-proto RSN             # Используем "усиленный" WPA, фактически - WPA2
  wpa-pairwise CCMP         # Используемый алгоритм шифрования
  wpa-key-mgmt WPA-PSK      # Используемый алгоритм аутентификации
  wpa-passphrase passphrase # Кодовая фраза, используемая в качестве общего ключа
Осталось поднять интерфейс:
# ifup wlan0
Если вы бываете в нескольких разных местах, где работают разные беспроводные сети, можно воспользоваться пакетом guessnet, который умеет обнаруживать беспроводные сети по их идентификаторам и MAC-адресам точек доступа. Я оставлю описание настройки guessnet за рамками этой статьи.

К сожалению, даже совместно с guessnet и ifplugd, мне не удалось настроить правильную обработку кнопки включения-выключения беспроводных устройств, имеющихся в ноутбуке. После повторного включения появляется две копии DHCP-клиента, так что сеть не удаётся настроить без захода под пользователем root.

Кроме ноутбука я пробовал подключать к сети планшет, сотовый телефон и цифровой мультимедийный проигрыватель. С мультимедийным проигрывателем воспроизведение видео иногда замирало или "рассыпалось на кубики", так что для просмотра видео стоит всё-же использовать проводную Ethernet-сеть, с которой таких проблем не бывает.

Ссылки