Пользователь
0,0
рейтинг
14 августа 2009 в 19:29

Администрирование → Настройка nginx

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

Неплохой начальной точкой для настройки nginx является конфиг, который идёт в комплекте с дистрибутивом, но очень многие возможности этого сервера в нём даже не упоминаются. Значительно более подробный пример есть на сайте Игоря Сысоева: sysoev.ru/nginx/docs/example.html. Однако, давайте лучше попробуем собрать с нуля свой конфиг, с бриджем и поэтессами. :)

Начнём с общих настроек. Сначала укажем пользователя, от имени которого будет работать nginx (от рута работать плохо, все знают :) )

user nobody;

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

worker_processes 2;

Уточним, куда писать логи ошибок. Потом, для отдельных виртуальных серверов, этот параметр можно переопределить, так что в этот лог будут сыпаться только «глобальные» ошибки, например, связанные со стартом сервера.

error_log /spool/logs/nginx/nginx.error_log notice; # уровень уведомлений "notice", конечно, можно менять

Теперь идёт очень интересная секция «events». В ней можно задать максимальное количество соединений, которые одновременно будет обрабатывать один процесс-воркер, и метод, который будет использоваться для получения асинхронных уведомлений о событиях в ОС. Конечно же, можно выбрать только те методы, которые доступны на вашей ОС и были включены при компиляции.

Эти параметры могут оказать значительное влияние на производительность вашего сервера. Их надо подбирать индивидуально, в зависимости от ОС и железа. Я могу привести только несколько общих правил.

Модули работы с событиями:
— select и poll обычно медленнее и довольно сильно нагружают процессор, зато доступны практически везде, и работают практически всегда;
— kqueue и epoll — более эффективны, но доступны только во FreeBSD и Linux 2.6, соответственно;
— rtsig — довольно эффективный метод, и поддерживается даже очень старыми линуксами, но может вызывать проблемы при большом числе подключений;
— /dev/poll — насколько мне известно, работает в несколько более экзотических системах, типа соляриса, и в нём довольно эффективен;

Параметр worker_connections:
— Общее максимальное количество обслуживаемых клиентов будет равно worker_processes * worker_connections;
— Иногда могут сработать в положительную сторону даже самые экстремальные значения, вроде 128 процессов, по 128 коннектов на процесс, или 1 процесса, но с параметром worker_connections=16384. В последнем случае, впрочем, скорее всего понадобится тюнить ОС.

events {
worker_connections 2048;
use kqueue; # У нас BSD :)
}

Следующая секция — самая большая, и содержит самое интересное. Это описание виртуальных серверов, и некоторых параметров, общих для них всех. Я опущу стандартные настройки, которые есть в каждом конфиге, типа путей к логам.

http {
# Весь код ниже будет внутри этой секции %)
# ...
}

Внутри этой секции могут находиться несколько довольно интересных параметров.

Системный вызов sendfile появился в Linux относительно недавно. Он позволяет отправить данные в сеть, минуя этап их копирования в адресное пространство приложения. Во многих случаях это существенно повышает производительность сервера, так что параметр sendfile лучше всегда включать.

sendfile on;

Параметр keepalive_timeout отвечает за максимальное время поддержания keepalive-соединения, в случае, если пользователь по нему ничего не запрашивает. Обдумайте, как именно на вашем сайте посылаются запросы, и исправьте этот параметр. Для сайтов, активно использующих AJAX, соединение лучше держать подольше, для статических страничек, которые пользователи будут долго читать, соединение лучше разрывать пораньше. Учтите, что поддерживая неактивное keepalive-соединение, вы занимаете коннекшн, который мог бы использоваться по-другому. :)

keepalive_timeout 15;

Отдельно стоит выделить настройки проксирования nginx. Чаще всего, nginx используется именно как сервер-прокси, соответственно они имеют довольно большое значение. В частности, размер буфера для проксируемых запросов имеет смысл устанавливать не менее, чем ожидаемый размер ответа от сервера-бэкенда. При медленных (или, наоборот, очень быстрых) бэкендах, имеет смысл изменить таймауты ожидания ответа от бэкенда. Помните, чем больше эти таймауты, тем дольше будут ждать ответа ваши пользователе, при тормозах бэкенда.

proxy_buffers 8 64k;
proxy_intercept_errors on;
proxy_connect_timeout 1s;
proxy_read_timeout 3s;
proxy_send_timeout 3s;

Небольшой трюк. В случае, если nginx обслуживает более чем один виртуальный хост, имеет смысл создать «виртуальный хост по-умолчанию», который будет обрабатывать запросы в тех случаях, когда сервер не сможет найти другой альтернативы по заголовку Host в запросе клиента.

# default virtual host
server {
listen 80 default;
server_name localhost;
deny all;
}

Далее может следовать одна (или несколько) секций «server». В каждой из них описывается виртуальный хост (чаще всего, name-based). Для владельцев множества сайтов на одном хостинге, или для хостеров здесь может быть что-то, типа директивы

include /spool/users/nginx/*.conf;

Остальные, скорее всего опишут свой виртуальный хост прямо в основном конфиге.

server {
listen 80;

# Обратите внимание, в директиве server_name можно указать несколько имён одновременно.
server_name myserver.ru myserver.com;
access_log /spool/logs/nginx/myserver.access_log timed;
error_log /spool/logs/nginx/myserver.error_log warn;
# ...

Установим кодировку для отдачи по-умолчанию.

charset utf-8;

И скажем, что мы не хотим принимать от клиентов запросы, длиной более чем 1 мегабайт.

client_max_body_size 1m;

Включим для сервера SSI и попросим для SSI-переменных резервировать не более 1 килобайта.

ssi on;
ssi_value_length 1024;

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

set $www_root "/data/myserver/root";

location / {
proxy_pass 127.0.0.1:9999;
proxy_set_header X-Real-IP $remote_addr;
proxy_intercept_errors off;
proxy_read_timeout 5s; # Обратите внимание, здесь мы переопределили глобальную настройку, заданную выше
proxy_send_timeout 3s;
# ...

Отдельный блок в корневом локейшне посвящён компрессии получаемого результата в gzip. Это позволит вам, и вашим пользователям сэкономить на трафике. Nginx можно указать, какие типы файлов (или, в нашем случае, ответов от бэкенда) стоит сжимать, и каким должен быть минимальный размер файла для того, чтобы использовать сжатие.

# ...
gzip on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/xml;
}

location /i/ {
root $www_root/static/;
}
}

Всем спасибо за внимание. И, сорри, что пост получился довольно длинным.
Иван Авсеянко @Rebus
карма
146,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Администрирование

Комментарии (53)

  • +4
    Спасибо за статью! Как раз хотел на своем VDS'е поднять nginx в качестве фронтэнда перед апачем.
    А вообще, было бы здорово обойтись без апача. Ждем статью про настройку nginx для работы с php-fpm. =)
    • –8
      Есть такая статья. Недавно поднял эту связку + eaccelerator + memcached, оттюнил это всё. Но карма поёт романсы… )))
      • –4
        Чесслово, если бы мог — добавил! Вы обладатель ценного опыта.))
        Есть предложение к сообществу поднять карму товарищу skachko в обмен на хороший материал.
      • +3
        Так вы пишите, не стесняйтесь. Потом просто кто то разместит вашу статью, если будет хорошая, то будет и карма :) Если надо — обращайтесь.

        Сам подобную связку некогда разворачивал, интересно будет узнать что вы там натюнили.
        • 0
          Спасибо за идею.
  • 0
    а по настройке через php cgi поможете?)
    • 0
      Ну, собственно, апач, который висит на порту 9999 (или любом другом), может выполнять и CGI-скрипты на PHP (или уж, всё-таки, под mod_php — это быстрее). Или я не понял вопроса?..
      • 0
        я имел ввиду запустиь php-cgi без apache, висящих с портами для подключения nginx
        • +10
          В этом нет ничего сложного. Ставим php5-cgi из портов. Создаём php-fastcgi ини-файл /etc/init.d/php-fastcgi:

          #! /bin/sh
          ### BEGIN INIT INFO
          # Provides: php-fastcgi
          # Required-Start: $all
          # Required-Stop: $all
          # Default-Start: 2 3 4 5
          # Default-Stop: 0 1 6
          # Short-Description: Start and stop php-cgi in external FASTCGI mode
          # Description: Start and stop php-cgi in external FASTCGI mode
          ### END INIT INFO

          # Author: Kurt Zankl

          # Do NOT «set -e»

          PATH=/sbin:/usr/sbin:/bin:/usr/bin
          DESC=«php-cgi in external FASTCGI mode»
          NAME=php-fastcgi
          DAEMON=/usr/bin/php-cgi
          PIDFILE=/var/run/$NAME.pid
          scriptNAME=/etc/init.d/$NAME

          # Exit if the package is not installed
          [ -x "$DAEMON" ] || exit 0

          # Read configuration variable file if it is present
          [ -r /etc/default/$NAME ] &&. /etc/default/$NAME

          # Load the VERBOSE setting and other rcS variables
          . /lib/init/vars.sh

          # Define LSB log_* functions.
          # Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
          . /lib/lsb/init-functions

          # If the daemon is not enabled, give the user a warning and then exit,
          # unless we are stopping the daemon
          if [ "$START" != «yes» -a "$1" != «stop» ]; then
          log_warning_msg «To enable $NAME, edit /etc/default/$NAME and set START=yes»
          exit 0
          fi

          # Process configuration
          export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS
          DAEMON_ARGS="-q -b $FCGI_HOST:$FCGI_PORT"

          do_start()
          {
          # Return
          # 0 if daemon has been started
          # 1 if daemon was already running
          # 2 if daemon could not be started
          start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null || return 1
          start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --background --make-pidfile --chuid $EXEC_AS_USER --startas $DAEMON — $DAEMON_ARGS || return 2
          }

          do_stop()
          {
          # Return
          # 0 if daemon has been stopped
          # 1 if daemon was already stopped
          # 2 if daemon could not be stopped
          # other if a failure occurred
          start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE > /dev/null # --name $DAEMON
          RETVAL="$?"
          [ "$RETVAL" = 2 ] && return 2
          # Wait for children to finish too if this is a daemon that forks
          # and if the daemon is only ever run from this initscript.
          # If the above conditions are not satisfied then add some other code
          # that waits for the process to drop all resources that could be
          # needed by services started subsequently. A last resort is to
          # sleep for some time.
          start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
          [ "$?" = 2 ] && return 2
          # Many daemons don't delete their pidfiles when they exit.
          rm -f $PIDFILE
          return "$RETVAL"
          }

          case "$1" in
          start)
          [ "$VERBOSE" != no ] && log_daemon_msg «Starting $DESC» "$NAME"
          do_start
          case "$?" in
          0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
          2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
          esac
          ;;
          stop)
          [ "$VERBOSE" != no ] && log_daemon_msg «Stopping $DESC» "$NAME"
          do_stop
          case "$?" in
          0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
          2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
          esac
          ;;
          restart|force-reload)
          log_daemon_msg «Restarting $DESC» "$NAME"
          do_stop
          case "$?" in
          0|1)
          do_start
          case "$?" in
          0) log_end_msg 0 ;;
          1) log_end_msg 1 ;; # Old process is still running
          *) log_end_msg 1 ;; # Failed to start
          esac
          ;;
          *)
          # Failed to stop
          log_end_msg 1
          ;;
          esac
          ;;
          *)
          echo «Usage: $scriptNAME {start|stop|restart|force-reload}» >&2
          exit 3
          ;;
          esac

          Делаем исполняемым — 755. Создаем еще один файл /etc/default/php-fastcgi:

          #
          # Settings for php-cgi in external FASTCGI Mode
          #

          # Should php-fastcgi run automatically on startup? (default: no)

          START=yes

          # Which user runs PHP? (default: www-data)

          EXEC_AS_USER=www-data

          # Host and TCP port for FASTCGI-Listener (default: localhost:9000)

          FCGI_HOST=localhost
          FCGI_PORT=9000

          # Environment variables, which are processed by PHP

          PHP_FCGI_CHILDREN=5
          PHP_FCGI_MAX_REQUESTS=1000

          Запускаем: /etc/init.d/php-fastcgi start

          Добавляем в автозагрузку: update-rc.d php-fastcgi defaults
          • 0
            спасибо)
            • 0
              Не за что. И минусяка за коммент прилетел. )
              Так и знал, что много кода некоторых людей испугает.
  • +3
    Вообще на сайте автора прекрасная документация, по ней вполне можно настроить большинство конфигураций (в том числе и с php-fastcgi).

    PS:
    >> а по настройке через php cgi поможете?)

    Просто CGI в nginx'е нету.
    • 0
      ну вы же все равно поняли, о чем речь ;)
    • 0
      Кстати да, я просто поленился ещё раз вставлять ссылку, но, вообще, документации много и в ней описано почти всё. :)
    • 0
      кстати CGI скрипты через скрипт-враппер прекрасно работают тоже. Используем около года уже на выделенных веб-серверах для клиентов ставим, от апача отказались полностью практически.
  • +2
    Как завещал господин Сысоев — чем меньше переменных — тем лучше.

    Посмотрите в сторону директивы <a href=«sysoev.ru/nginx/docs/http/ngx_http_core_module.html#alias'>alias
    • +2
      Парсер лох!
    • 0
      Конечно, количество переменных лучше свести к минимуму, однако, в случае сколько-то большого конфига (локаций хотя бы на 30), вы замучаетесь писать этот алиас с одним и тем же путём 30 раз. А если внезапно понадобится переложить document root в другой каталог, так и вообще проклянёте всё-всё-всё. Некошерно делать поиск и замену по всем местам конфига, там где можно просто завести переменную, тем более, что оверхед в данном случае стремится к нулю.
      • +2
        Я бы переписал ваш конфиг так:

        root /data/myserver/root

        location / {

        fastcgi_param PATH_TRANSLATED $document_root/$fastcgi_script_name;

        }

        location /i/ {
        alias /static/
        }

        p.s. переменная $document_root — системная
        • +2
          Да, пожалуй с $document_root будет лучше. :)
  • 0
    Сколько статей читаю про настройку nginx, но всеравно большинство моментов сводятся к тому, что «устанавливается экспериментальным путем».
    Таков вот инджинкс, былобы интересно собрать коллекцию «быстрых» конфигов с различных хостингов с описанием задачи выполняемой сервером, чтобы можно было от чегото отталкиваться при настройке.
  • +2
    если у вас большой объем куков (корзина магазинная, всякие данные профиля пользователя и т.п.) — обязательно явным образом настройте буферы для заголовков — по умолчанию они довольно малы…
    • 0
      Ну, в большинстве случаев, 1 килобайта под заголовки хватает. Но если нет, действительно, надо увеличить размер буфера заголовков директивой client_header_buffer_size. Естественно, это скажется на количестве памяти, потребляемой nginx, поскольку, AFAIK, этот объём памяти резервируется на каждый запрос.
  • 0
    А есть ли какие-то тонкости для кеширования статики браузером? У меня какая-то странная ситуация:

    Server: nginx/0.6.14
    Date: Fri, 14 Aug 2009 18:40:49 GMT
    Content-Type: image/gif
    Content-Length: 5659
    Last-Modified: Sun, 17 May 2009 20:28:33 GMT
    Connection: close
    Expires: Sat, 14 Aug 2010 18:40:49 GMT
    Cache-Control: max-age=31536000, public
    Accept-Ranges: bytes


    Вроде как картинка должна насмерть закешироваться, но Firebug упорно показывает код 200, а не 304. То есть картинки, стили и скрипты грузятся каждый раз.
    • 0
      Это половые проблемы исключительно firebug, поскольку вы же смотрите скорость загрузки файлов. curl или livehttpheaders показывает актуальную инфу.
      • 0
        Нет, я смотрю не на скорость, а на заголовки, которые отображает firebug. И в сравнении с webo.in я просто уверен, что у меня все грузится каждый раз снова :-)

        Хотя попробую посмотреть еще разными средствами. Спасибо за совет!
      • +1
        Кстати webo.in тоже говорит, что кеширование статики отключено. Он меня и направил по сути на обстоятельное расследование.
        • 0
          Попробуйте проставить Etag и Cache-Control private
          • 0
            Допустим Cache-Control ставить я уже пробовал многими способами и сейчас снова попробовал private даже, но все работает по прежнему. Изменилась только строка:

            Cache-Control max-age=31536000, private

            А вот параметра Etag вообще нет. Модуль Etag включается только одной строкой:

            FileETag on;

            Что в документации ETag и написано.
        • 0
          Странно, поскольку заголовок Expires вроде бы устанавливается правильно. Может быть дело в «Cache-Control»?
          • 0
            Может быть… но я уже множество вариантов перепробовал.
    • 0
      Поставьте LiveHTTPHeaders, включите сайдбар. Зайдите один раз на страничку. Нажмите не «Обновить», а в адресной строке ещё раз. Насладитесь тем, что запроса к серверу вообще не было. :) «Обновить» в большинстве браузеров означает безусловный приказ перезагрузить страничку, забив на кеш, что и показывает Firebug.
      • 0
        Парсер съёл Enter в треугольных скобках. Ой. :)
      • 0
        Опять же можно сравнить с webo.in, да и многие другие ресурсы. При обновлении кругом 304, то есть взято из кеша. :-)
      • 0
        Кстати, поставил LiveHTTPHeaders — результат тот же самый — 200. Попробовал через адрес — все грузится снова :))
    • –1
      Что бы получить код 304 нужен ETag (но это не значит, что браузер не будет кешировать статику в вашем случае)
      • 0
        Спасибо, буду пробовать :-)
      • 0
        Не сработало — ситуация осталась прежней :(
  • +4
    При отдаче большого количества файлов nginx'ом крайне полезно настроить кеш открытых дескрипторов файлов.
    Например, так:

    open_file_cache max=1000 inactive=300s;
    open_file_cache_valid 360s;
    open_file_cache_min_uses 2;
    open_file_cache_errors off;

    Более подробно в документации:
    sysoev.ru/nginx/docs/http/ngx_http_core_module.html#open_file_cache

    До того, как настроил это, nginx на отдаче кучи мелких ssi-файлов грузил машину больше, чем апач с file_cache и disk_cache :)
  • 0
    Это все и без статьи известно и можно найти в инете.
    А вот чем kqueue от epoll отличается, что лучше и в каких ситуациях? Более интересны тонкости и советы при работе с высоконагруженными системами.
    • +1
      kqueue — лучше для бздей, а epoll — лучше для линукс систем =) А так они очень похожи.

      Могу поделится решением одной проблемы — на линуксе при большой интерсивности запросов и включенном keep-alive засоряется conntrack таблица и в определенный момент соеденения начинают отвергатся. Можно просто поднять размер таблици, но лучше оттюнить ipv4 в ядре. К примеру (актуально для фронтенда с nginx):

      # спсибо человеку, который поделился этими настройками
      net.ipv4.tcp_sack = 1
      net.ipv4.tcp_fack = 1
      net.ipv4.tcp_timestamps = 1
      net.ipv4.tcp_wmem = 4096 65536 524288
      net.ipv4.tcp_rmem = 4096 87380 524288
      net.ipv4.tcp_fin_timeout = 1
      net.ipv4.tcp_tw_recycle = 1
      net.core.somaxconn = 262144
      net.ipv4.tcp_syncookies = 1
      net.ipv4.tcp_synack_retries = 2
      net.ipv4.tcp_syn_retries = 2
      net.core.netdev_max_backlog = 40000
      net.ipv4.tcp_no_metrics_save = 1
      net.ipv4.netfilter.ip_conntrack_max = 2097152
      kernel.sem = 1250 256000 100 1024
      • 0
        да, про sysctl в курсе. вот такие бы все советы и собрать в одну статью :)
        за разъяснение различий между epoll и kqueue спасибо.
      • 0
        Не лучше ли не использовать правила требующие conntrack? Большинству они не нужны.
        • 0
          Если не нужны — то можно и не использовать=), отключив трекинг конекшенов для указаных портов (iptables -t raw -A PREROUTING -p tcp --dport 80 -j NOTRACK)
          • 0
            Если банально не загружать модуль, то ничего и не расходуется.
  • +1
    > Если ожидается высокая нагрузка на жёсткий диск, можно сделать по процессу на
    > каждый физический жёсткий диск, поскольку вся работа будет всё-равно ограничена
    > его производительностью
    Эм… кхм…
    воздержусь все же, пожалуй. :-)
    • 0
      Ну, собственно, в большинстве случаев, это будет довольно плохой совет. :) Хорошим он может быть только под весьма специфической нагрузкой, типа файлового хостинга.
      • +1
        Ну я бы не сказал, что совет прямо «плохой». Он ИМХО просто неправильный. Ну как число процессов связано с загруженностью дисков? По-первых, есть sendfile, который специально сделан, чтобы по минимуму грузить процессор и память. Во-вторых, даже с sendfile=off (и при условии, что nginx читает данные с диска синхронно, в чем я не уверен) получается очень странная картина. Вот представьте: пришло 2 запроса в 2 разных процесса, и оба процесса пошли читать данные с диска (долго — диск перегружен). При этом приходят еще 10 запросов, но они не могут начать обрабатываться, т.к. оба процесса уже «заняты». Среди этих 10 запросов, может, 9 попадают в кэш и мгновенно отдадут контент. Ан нет, их заблокировали.
        • 0
          Извините, что задержался с ответом — ваш комментарий погрузил меня в лёгкую задумчивость на целых два дня. :)

          В общем, дело в том, что, на текущий момент, почти весь дисковый ввод-вывод в Nginx — синхронный. Игорь сейчас работает над тем, чтобы переделать его на AIO, но результат может появиться ещё не скоро.

          Синхронность работы с ФС, при обычной нагрузке веб-сервера, не составляет проблемы, поскольку обычно отдаётся относительно небольшое количество мелких файлов, которые прекрасно размещаются в кеше ОС, и читаются оттуда практически мгновенно. Тем более, что системные вызовы write* итак реализованы в той или иной степени асинхронно (об этом обычно заботится драйвер файловой системы).

          А вот в случае нагрузки, характерной для файлового хостинга — большое количество больших файлов, которые не помещаются в кеш, и запрашиваются почти рандомно — имеет смысл создавать «по процессу на жёсткий диск» именно для того, чтобы избежать блокировок на дисковом вводе-выводе. Это не очень хорошо, но, если подумать, в этом случае тормозом, в любом случае, станет жёсткий диск, а эту проблему никаким асинхронным вводом-выводом не решить.

          Кстати, системный вызов sendfile работает в большой степени синхронно. Он позволяет избежать лишнего копирования данных в адресное пространство процесса, но, не выполняет отдачи данных «совсем в фоновом режиме» (впрочем, если работать с асинхронными сокетами, ситуация, кажется, немного меняется).
  • 0
    Вот это:

    proxy_pass 127.0.0.1:9999;

    Надо поменять на

    • 0
      proxy_pass 127.0.0.1:9999;
      • 0
        понятно, хабрапарсер правит слегка

        в общем там еще h_t_t_p:// перед ip
        • 0
          оо, спасибо!

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