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

Установка Redmine в Debian Wheezy

Эту заметку отчасти можно рассматривать как продолжение заметки Настройка nginx, php5-fpm и uwsgi. Здесь я покажу, как можно запустить Redmine под управлением uwsgi.

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

Написана эта система на языке Ruby и с использованием очень известного за пределами Ruby-мира веб-фреймворка Rails. Запустить дистрибутивный Redmine мне удалось далеко не с первого раза: сначала появлялась какая-то ошибка обработки UTF-8, которую удалось решить переключением с Ruby1.9.1 на Ruby1.8, потом я долго не мог справиться с неполной передачей данных между веб-сервером и uwsgi, которая решилась указанием специальной опции буферизации uwsgi.

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

Для начала установим необходимые пакеты:
# apt-get install redmine redmine-mysql ruby1.8 uwsgi uwsgi-plugin-rack-ruby1.8 nginx-full
Как уже понятно из состава установленных пакетов, в качестве СУБД будем использовать MySQL. В процессе установки запустится мастер настройки базы данных. Ниже приведены снимки экрана с его диалоговыми окнами:







Настройка uwsgi

Для запуска Redmine под управлением uwsgi создадим файл /etc/uwsgi/apps-available/redmine.ini со следующим содержимым:
[uwsgi]

master = true
procname = uwsgi-redmine
procname-master = uwsgi-redmine-master
  
plugin = uwsgi_rack_ruby18
rails = /usr/share/redmine/
env = RAILS_ENV=production
env = RAILS_RELATIVE_URL_ROOT=/redmine
processes = 4
post-buffering = 1
Описание опции post-buffering, с помощью которой удалось решить проблему с неполной передачей данных между веб-сервером и uwsgi, найденное на странице Things to know (best practices and “issues”) READ IT !!!:
If an HTTP request has a body (like a POST request generated by a form), you have to read (consume) it in your application. If you do not do this, the communication socket with your webserver may be clobbered. If you are lazy you can use the post-buffering option that will automatically read data for you. For Rack applications this is automatically enabled.
Несмотря на то, что тут написано что эта опция автоматически включается для Rack-приложений, на практике мне пришлось включить её вручную. Возможно автоматическое включение опции было добавлено в более поздних версиях uwsgi.

Переменная окружения RAILS_RELATIVE_URL_ROOT позволяет настроить Redmine для работы из подкаталога веб-сервера. Если вы запускаете Redmine на отдельном виртуальном или выделенном сервере, то эту строку указывать не нужно.

Осталось включить приложение:
# cd /etc/uwsgi/apps-enabled
# ln -s /etc/uwsgi/apps-available/redmine.ini .
# /etc/init.d/uwsgi start redmine
Настройка веб-сервера

Для начала приведу пример файла конфигурации для настройки Redmine для работы из корня сайта:
server {
  listen 80;

  server_name redmine.domain.tld;

  location / {
    alias /usr/share/redmine/public/;
    try_files $uri @redmine;
  }

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

    uwsgi_modifier1 7;
  }
}
Этот файл представляет собой настройки отдельного сайта, поэтому его нужно поместить в отдельный файл в каталоге для сайтов nginx. В данном случае это будет файл /etc/nginx/sites-available/redmine. Чтобы подключить его использование в nginx, нужно создать символическую ссылку:
# cd /etc/nginx/sites-enabled
# ln -s /etc/nginx/sites-available/redmine .
Осталось перезагрузить или перезапустить nginx, чтобы настройки нового сайта вступили в силу:
# /etc/init.d/nginx restart
Можно настроить Redmine для работы из подкаталога /redmine. Для этого нужно вписать следующие настройки в файл конфигурации уже существующего сайта:
location /redmine/ {
    alias /usr/share/redmine/public/;
    try_files $uri @redmine;
  }

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

    uwsgi_modifier1 7;
  }
Осталось перезагрузить nginx, чтобы новые настройки вступили в силу:
# /etc/init.d/nginx reload
Redmine

Теперь можно зайти в Redmine:

В системе уже создан пользователь с логином admin и таким же паролем. Не забудьте сразу же после входа поменять пароль!

воскресенье, 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.