Autoscaling — инструмент автоматического вертикального масштабирования ресурсов (CPU|RAM|HDD)

    В начале этого года у наших облачных VDS появился публичный API. Он позволяет клиенту делать практически все те же действия с облачными виртуальными машинами и дисками, что и в панели: создавать, удалять диски и ВМ, изменять тарифы и размер диска и тп.





    Вместе с появлением API появилась и идея на его основе реализовать систему мониторинга за ресурсами виртуальной машины (ВМ), работающую внутри машины, и автоматического увеличения/уменьшения необходимых ресурсов по необходимости — автоскейлинг (autoscaling, АS).
    Некоторые пояснения по AS
    Здесь стоит уточнить, что поскольку система AS основана на API, в ее задачи не входит мгновенное предоставление ресурсов по требованию, в момент появления необходимости в них, или угадывание/предсказывание будущей необходимости в ресурсах. Суть AS заключается в том, что он должен зафиксировать момент, когда можно с уверенностью сказать, что ресурсов текущего тарифного плана становится недостаточно для гарантированного* и своевременного** выполнения запущенных на виртуальной машине процессов, и автоматически перевести ВМ на следующий тариф.

    **Своевременного — потому что, если выполнение запущенных процессов упирается в процессорный ресурс, то процессы так или иначе выполнятся. Но время их завершения становится непредсказуемым.
    *Гарантированного, потому что если оперативная память ВМ близка к исчерпанию и на ВМ не настроен swap, то это означает, что близка ситуация, когда какой-то из запущенных на ВМ процессов будет аварийно завершен операционной системой, если суммарное потребление памяти всеми процессами превысит ее общий объем. Если же swap настроен, то пока он также не исчерпается, никто убит не будет, но быстродействие ВМ также сильно просядет, т.к. будет зависеть от скорости работы swap-раздела, которая в любом случае на порядок меньше, чем скорость работы оперативной памяти.


    Для того, чтобы понять, что ситуация близка к критической, AS должен провести наблюдения в течение некоторого времени. Ведь ситуация с мгновенным кратковременным всплеском нагрузки, скорее всего не является хорошим поводом для смены тарифа. Таким образом, стоит учитывать, что когда AS понимает, что ситуация является критической, вполне вероятно, что некоторая нехорошая ситуация уже случилась (страничка вашего сайта уже минуту не загружается у какого-то посетителя, или какие-то процессы на вашей ВМ уже были аварийно завершены OOM-Killer-ом — подсистемой ядра linux, выбирающей, какой процесс надо принести в жертву, чтобы остальная система продолжала работать, если оперативная память исчерпана). То есть, AS не является серебряной пулей, гарантированно защищающей ваши сервисы от недостатка ресурсов. Но он настроен таким образом, чтобы свести к минимуму негативные последствия от исчерпания ресурсов за счет своевременного их расширения. Он сделает это наверняка быстрее, чем вы сами заметите или даже узнаете, что на вашем сервере появились проблемы и успеете дотянуться до панели управления ВМ, чтобы руками расширить ресурсы. Больше того, в большинстве случаев (все зависит от профиля возрастающей нагрузки) AS переключит тариф раньше, чем нехватка ресурсов успеет проявиться явным негативным образом.

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

    Аналогично с диском. Мы постараемся расширить его раньше, чем на нем закончится место. В случае с диском автоматика работает только в сторону увеличения. Уменьшение файловой системы — это offline-операция, требующая остановки виртуальной машины. Вы по прежнему сможете это сделать через панель управления, когда это вам понадобится.

    Мы разделили возможность включения AS по разным критериям. Например, вы можете отдельно включить autoscaling по критерию памяти, но не включать его по процессору, если считаете, что быстродействие запущенных внутри вашей ВМ процессов не столь критично, как стабильность их работы, и не требует дополнительных затрат денег. Например, если у вас на виртуальной машине регулярно запускается какой-то тяжелый долгоиграющий процесс, скажем, выгрузка большого массива данных в базу, или другая плановая обработка большого массива данных, то вам, возможно, не так принципиально, когда именно завершится этот процесс: через час или через два, но гораздо более критично, что бы этот процесс завершился в принципе, а не был убит случайно операционной системой в случае недостатка оперативной памяти.

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

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

    Давайте рассмотрим подробнее, как все это работает.

    Итак, в нашем случае autoscaling — это набор python-скриптов, запускающихся по cron-у. При запуске они читают предыдущую историю замеров, производят дополнительную необходимую диагностику системы, сохраняют обновленную историю для следующего запуска, производят оценку полученных данных и при необходимости производят действия по изменению тарифа или расширению диска, либо просто завершаются.

    Autoscaling диска.
    К виртуальной машине может быть подключено несколько дисков. Autoscaling можно включать отдельно для каждого диска, и отдельно выставлять лимит, до которого диск может быть автоматически расширен.

    Ограничения:
    По умолчанию на новом диске ставится ФС ext4, но пользователь может позже на свой диск установить любую другую ФС, таблицу разделов, lvm или что-то еще, что ему необходимо. Поскольку ФС должна, так же как и сам диск, расширяются автоматически (иначе теряется суть AS), существует риск предпринять какие-либо деструктивные действия по отношению к тому, что установлено на диске и в самом неприятном случае это может привести, в том числе, к потере данных. Поэтому мы решили ограничить возможность работы AS только штатной ФС ext4.

    Также мы не даем включать autoscaling для диска на системах, основанных на Centos6, так как в них используется слишком старое ядро 2.6, в котором некорректно работает udev, в следствие чего файловая система автоматически не расширяется после расширения диска.

    Механизм работы:
    По cron-у раз в 7 минут запускается autoscaling по диску. Из всех доступных источников собирается информация о всех подключенных к системе дисках.
    1. При настройке AS в панели, на виртуальную машину отправляется конфиг в формате json /etc/autoscaling/autoscaling.conf. Там есть перечень всех дисков, подключенных к данной ВМ (их uid), информация о том, включен ли для них autoscaling и до какого максимума их можно расширять;
    2. Внутри ВМ из /sys/class/block берем перечень всех дисков, видимых в системе;
    3. Из /proc/partitions берем размеры дисков;
    4. Из /proc/mounts извлекаем информацию о типе ФС и о том, смонтирована ли она в режиме read-write;
    5. Из вывода df получаем информацию о размере ФС и о том, сколько на ней осталось свободного места.
    В итоге, в результате сбора информации у нас должно получиться что-то вроде заполненной таблицы по всем дискам:
    Disk  id    disk_size  max_size  autoscale_enabled  fs_type  is_writable  fs_size  fs_free
    ------------------------------------------------------------------------------------------
    vda   1081  5          40        True               ext4     True         5        1.2
    vdb   None  0.1        None      None               iso9660  False        0.1      0.1
    vdc   1082  10         45        True               ext4     True         10       6.2
    vdd   1234  5          15        False              ext4     True         5        0.3
    


    Если нам удалось собрать всю нужную информацию по диску, на нем находится подходящая ФС (ext4) и если для него включен AS, то выполняется проверка: сколько осталось процентов свободного места на ФС. Если это значение оказывается меньше пороговых 10%, то инициируется отправка вызова API в панель на увеличение данного диска. Панель отправляет соответствующую команду на мастер-сервер, где работает данная ВМ, об увеличении данного диска. (А также панель отправляет пользователю письмо на контактный email о данном событии со всеми необходимыми подробностями). При расширении диска udev внутри пользовательской системы перехватывает это событие и запускает процесс расширения ФC (resize2fs).

    Все действия AS логируются внутри виртуальной машины в /var/log/syslog и в логах панели.

    Autoscaling тарифов.

    Для изменения памяти или процессора применяется переход на другой тариф. У нас есть линейка тарифов, которую мы можем получать через API в формате json.
    Выглядит на данный момент она вот так:
    [
        {
            "memsize": 1024,
            "name": "tiny",
            "ncpu": 2
        },
        {
            "memsize": 2048,
            "name": "small",
            "ncpu": 2
        },
        {   
            "memsize": 4096,
            "name": "medium",
            "ncpu": 4
        },
        {
            "memsize": 8192,
            "name": "large",
            "ncpu": 4
        },
        {
            "memsize": 8192,
            "name": "xl8",
            "ncpu": 8
        },
        {
            "memsize": 16384,
            "name": "xl16",
            "ncpu": 8
        }
    ]
    


    Как видно, некоторые переходы на соседний тариф не дают увеличения/уменьшения одного из параметров (cpu/memsize). Например, переход с «TINY» на «SMALL» увеличивает только объем памяти, но не добавляет CPU. Для того, чтобы увеличить CPU находясь на «TINY», надо перейти сразу на «MEDIUM». Это учитывается при выборе нужного тарифа.

    Autoscaling тарифов. Память.

    Информация по потреблению памяти берется из /proc/meminfo.

    На основе параметров 'MemTotal', 'MemFree', 'Buffers', 'Cached' и 'Shmem' находим, сколько памяти на данный момент израсходовано и не может быть освобождено при необходимости.

    'Used' = 'MemTotal' — 'MemFree' — 'Buffers' — 'Cached' + 'Shmem'

    'Buffers' — это память, отведенная на временное хранение небольших дисковых блоков. Она используется для ускорения операций ввода-вывода с диском и эта память может быть оперативно освобождена операционной системой при необходимости (если какому-то из процессов потребуется памяти больше, чем имеется неиспользуемой памяти).

    'Cached' — кэш файлов, прочитанных с диска. Эта память также может быть оперативно освобождена.

    Но есть также 'Shmem' — разделяемая память, используемая, как самый быстрый способ межпроцессного взаимодействия (IPC). В /proc/meminfo 'Shmem' является частью 'Cached', но эта часть памяти не может быть оперативно освобождена, пока она используется каким-то из процессов.

    Тогда процент свободной памяти вместе с той, которая может быть освобождена по требованию, можно посчитать как:
    ('MemTotal' - 'Used') * 100 / 'MemTotal'

    или
    ('MemFree' + 'Buffers' + 'Cached' - 'Shmem') * 100 / 'MemTotal'

    Для принятия решения об изменении тарифа в бóльшую сторону мы делаем несколько замеров раз в минуту, каждый раз сохраняя историю предыдущих замеров. Если среднее значение свободной памяти за последние 5 минут окажется меньше пороговых 20%, то мы считаем, что ВМ пользователя уже стабильно перешла в критическую зону, и во избежание дальнейших проблем пора увеличить тариф до следующего тарифа, где памяти больше, чем на текущем. Применяется соответствующий вызов API. Тариф меняется практически мгновенно, без перезагрузок, и в вашей системе сразу же становится существенно больше памяти. Пользователю отправляется письмо на контактный email о произошедшем событии.

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

    Также, все действия AS логируются в /var/log/syslog и в логах панели.

    Autoscaling тарифов. Процессор.

    Статистика по потреблению CPU собирается раз в минуту и учитывается история за последние 10 минут. Статистика по CPU берется из /proc/stat, но интерпретировать ее несколько сложнее, чем в случае с памятью.

    Дело в том, что для более точного распределения ресурсов мы выдаем виртуальной машине не фактические ядра процессора мастер-сервера, а выдаем всегда любой виртуальной машине 12 ядер мастер-сервера и при помощи cgroups лимитируем, сколько процентов от одного ядра данный процесс виртуальной машины может потребить (Как известно, cgroups — это технология ядра Linux, позволяющая ограничить предоставление ресурсов для процессов или групп процессов ОС). Так что, если на тарифе «MEDIUM» мы предоставляем 4 ядра CPU для ВМ, то это значит, что фактических ядер будет предоставлено 12, и при помощи cgroups будет ограничено потребление CPU четырьмястами процентами от одного ядра. Данный подход дает преимущества в точности и гибкости предоставления ресурсов, так как если мы выдаем виртуальной машине фактические ядра, и на одном ядре работает больше одной ВМ, то они в итоге делят ресурс этого ядра CPU между собой, тогда как лимитирование при помощи cgroups позволяет выдать виртуальной машине ровно то, что обещано (при условии, что мы следим за загруженностью процессора на мастер-серверах и своевременно мигрируем виртуальные машины между более и менее загруженными мастер-серверами). Также это позволяет выдать не целое число ядер CPU, а, положим, 123% от одного ядра (мы этим не пользуемся).

    С дугой стороны, у этого подхода есть и минус — менее очевидна становится ситуация для владельца виртуальной машины. Так как в /proc/cpuinfo, в /proc/stat и во всех утилитах типа top, htop, atop, пользователь видит 12 ядер CPU. И чтобы корректно оценить ситуацию при помощи подобных утилит, требуется чуть больше понимания сути этих лимитов (впрочем, для удобства пользователей, в панели управления выводится вся необходимая информация в более привычном виде).
    Итак, из /proc/stat мы видим примерно такую картину:
    cpu  308468 0 155290 2087891920 139327 0 1717 323604 0 0
    cpu0 52005 0 18792 173912232 41000 0 734 40868 0 0
    cpu1 20211 0 23423 173905770 5134 0 253 47490 0 0
    cpu2 58747 0 22624 173929843 17311 0 162 35843 0 0
    cpu3 46602 0 17294 173965248 16777 0 100 31919 0 0
    cpu4 21629 0 9426 174009578 8572 0 66 21842 0 0
    cpu5 17057 0 10685 174021499 6986 0 53 18868 0 0
    cpu6 14844 0 8881 174011454 5011 0 58 25400 0 0
    cpu7 17524 0 10358 174023057 6182 0 66 20343 0 0
    cpu8 15126 0 8597 174030215 7034 0 47 19455 0 0
    cpu9 15437 0 9150 174023863 10817 0 56 20722 0 0
    cpu10 15456 0 8545 174028209 8363 0 62 21390 0 0
    cpu11 13826 0 7511 174030947 6135 0 56 19460 0 0
    


    Причем, информация о 12 ядрах в данном случае бесполезна. Достаточно первой строки, суммирующей информацию для всех ядер.
    cpu  308468 0 155290 2087891920 139327 0 1717 323604 0 0
    

    Числовые поля данной строки означают по порядку следующее:
    user — процессорное время, затраченное на обработку процессов в пространстве пользователя.
    nice — то же самое, но для процессов с измененным приоритетом (nice).
    system — время, затраченное на выполнение системных вызовов.
    idle — время в режиме простоя (пока процессор не занят никакими другими задачами из этого списка).
    iowait — время, проведенное процессором в ожидании операций ввода-вывода.
    irq — время на обработку прерываний.
    softirq — некие «легкие» прерывания. (Очевидно, чтобы понять, что это такое, надо выпить столько же плохой водки, сколько и Алексей Кузнецов во время их написания).
    steal — время, недополученное данной виртуальной машиной в связи с тем, что мастер отдал этот ресурс другой ВМ (и/или сработал cgroups-лимит, как в нашем случае).
    guest — время, потраченное для работы виртуального CPU для гостевой системы под управлением Linux (этот параметр имеет смысл только на мастер-системе).
    guest_nice — аналогично, для гостевых систем с измененным приоритетом.

    А также для подсчета статистики по CPU, кроме информации из /proc/stat необходима информация о том, сколько на самом деле ядер CPU выделено при помощи cgroups для данной ВМ. Тривиальным способом изнутри самой ВМ это не узнать.

    Поэтому, используется еще один вызов API, выдающий основную информацию о виртуальной машине.
    В json ее можно представить так:
    {
    "id": 11004,
    "ips": [
    {
    
    "id": 11993,
    "ipvalue": "185.41.161.231"
    }
    ],
    "memsize": 1024,
    "monitoring_enabled": false,
    "name": "esokolov_AS_test",
    "ncpu": 2,
    "ssh_keys": [
    42
    ],
    "state": "active",
    "storages": [
    12344
    ],
    "type": "tiny",
    "vm_id": "vm_40b5d315"
    }
    


    Для возможности скейлинга вниз так же надо знать, сколько ядер будет в случае перехода на меньший тариф. Эта информация берется из таблицы тарифов, приведенной ранее. Чтобы каждую минуту не дергать API с одними и теми же запросами информации от панели (о тарифах и о параметрах данной ВМ), эта информация кэшируется. До изменения тарифа информацию из кэша можно вполне считать актуальной. Кэш сбрасывается в случае смены тарифа, перезагрузки системы или в случае, если он хранится дольше заданного времени (сутки в нашем случае).

    В /proc/stat информация хранится в виде простых целочисленных счетчиков времени (единица — это одна сотая доля секунды на большинстве архитектур) от старта системы.

    Так что, хранение истории нам понадобится не только для получения статистики за последние 10 минут, но и чтобы понять, сколько было израсходовано ресурсов в каждый конкретный период измерений (вычитанием значения предыдущего периода из текущих значений. Назовем эти величины 'cur_user', 'cur_nice', 'cur_system' итд).

    Итак, из /proc/stat мы можем вычислить общий объем ресурса CPU (назовем его 'total').
    'total' = 'user' + 'nice' + 'system' + 'idle' + 'iowait' + 'irq' + 'softirq' + 'steal'
    

    Общий объем ресурса CPU за текущий период времени (назовем его 'cur_total' получается вычитанием 'total' от предыдущего замера из 'total' текущего замера). Это ресурс всех 12 ядер. При этом, часть, отданная данной виртуальной машине ('my_cur_total'), будет равна:
    'my_cur_total' = 'cur_total' * ncpu / 12
    

    где ncpu берется из информации о ВМ (cgroups-лимит в ядрах).

    Теперь мы можем рассчитать настоящий 'idle' (время бездействия CPU) текущего периода времени (назовем его 'my_cur_idle') для данной VM.

    'my_cur_idle' = 'my_cur_total' - 'cur_user' - 'cur_nice' - 'cur_system' - 'cur_iowait' - 'cur_irq' - 'cur_softirq'
    


    Понятно, что эта величина имеет мало общего с 'idle' из /proc/stat, т.к. там эта величина означает время простоя всех 12 ядер.

    Величины 'my_cur_idle', 'cur_iowait', 'cur_steal', и т.п., выраженные в процентах от 'my_cur_total' — это по сути те величины, которые обычно показывают утилиты типа top, atop, htop.
    Они же и являются основанием для переключения тарифа. То есть, вычисляется текущий процент свободного CPU и записывается в истории замеров. Если среднее значение этого процента за последние 10 минут опускается ниже критических 10%, то мы переключаем тариф на тариф с бóльшим количеством CPU. То есть, основанием для переключения вверх является ситуация, когда на виртуальной машине за последние 10 минут потреблялось в среднем более 90% CPU.

    Длительности замеров и пороговые значения, конечно, взяты эмпирически из предположения, что например разовый запуск тяжелого скрипта, съедающего весь CPU и завершающего свою работу в течение пары минут не является хорошим основанием для переключения тарифа. Впрочем, эти параметры могут быть отрегулированы по отзывам наших пользователей (скрипты AS и настройки автоматически обновляются на виртуальных машинах при выходе новых версий).

    Autoscaling тарифа вниз.

    Чтобы на ходу спуститься на предыдущий тариф с меньшим объемом памяти или с меньшим количеством CPU и не устроить при этом ад для системы, мы должны быть уверены, что на предыдущем тарифе текущая нагрузка будет полностью вписываться в имеющиеся ресурсы и по процессору, и по памяти, При этом будет оставаться некоторый запас для того, чтобы ситуация на меньшем тарифе не стала критической сразу же после переключения. Из этого, во-первых, следует, что вниз мы можем переключаться за раз только на ближайший тариф (если есть возможность переключиться на два тарифа вниз, это будет сделано за два шага). А, во-вторых, это означает, что все ранее приведенные расчеты по памяти и по процессору мы должны также делать и основываясь на параметрах предыдущего тарифа (на том, сколько в нем предоставляется памяти и CPU). Соответственно, эти параметры берутся из таблицы тарифов. И все приведенные выше рассуждения для получения критериев загруженности системы мы одновременно ведем с учетом этих параметров предыдущего тарифа. А также сохраняем их историю. То есть, в итоге мы одновременно получаем значения, которые были бы посчитаны для данной виртуальной машины при ее текущей нагрузке, если бы она была на предыдущем тарифе.

    Но для исключения постоянного переключения тарифов туда-сюда пороговые значения для переключения вниз чуть отличаются от пороговых значений для переключения вверх.
    Так для переключения вниз на меньшем тарифе после переключения должно остаться минимум 20% неизрасходованного CPU (idle) и минимум 30% свободной памяти (или той, которая может быть освобождена — буферы и дисковый кэш). И основанием для переключения также является не разовый замер, а средний результат по последним 10 измерениям.

    PS. Если вы уже наш клиент, то подключить autoscaling можете в панели управления — bit.ly/Panel-NetAngels.
    Для регистрации нового аккаунта вам сюда

    Only registered users can participate in poll. Log in, please.

    Какова вероятность, что вы воспользуетесь autoscaling`ом для своей ВМ?

    NetAngels
    24.59
    Пожалуй, лучший облачный хостинг в России
    Share post

    Comments 11

      0
      А можно на уровне обезьяны.
      Автошкалинг у вас в панельке включается или через API исключительно самим клиентом?
        0
        Опция включается из панели.
        Для пользования вам необходимо создать виртуальную машину, подключить диск и в разделе управления ВМ и дёрнуть переключить в соответствующем разделе в положение «ВКЛ»
          0
          А не проще было не выдумывать велосипед, а просто биллить потребленные ресурсы.
        0
        Перешел я по вашей ссылке VDS посмотреть, а там цены в списке конфигураций не совпадают с ценами в «интерактивном» (с ползунками) режиме. Разница почти в 3 раза притом. Это такая задумка?
          0
          Можем допустить, что вы сначала посмотрели тарифы виртуального хостинга , а затем тарифы облачных VDS.
          Это две разных услуги и стоят они по-разному.
            0
            Со страницы тарифов облачных VDS я никуда не уходил. Возможно, что-то не так понял, на всякий случай прикладываю нотариально заверенные скриншоты:

            «Tiny» в окне с ползунками ~295р
            https://habrastorage.org/files/65f/442/f73/65f442f734274e0c9d761a0cae2c870b.PNG

            «Tiny» в списке конфигураций ~130р
            https://habrastorage.org/files/523/59c/36d/52359c36d1344a1c831f86ce7a8ed7e5.PNG
              0
              «Tiny» в окне с ползунками ~295р
              https://habrastorage.org/files/65f/442/f73/65f442f734274e0c9d761a0cae2c870b.PNG

              «Tiny» в списке конфигураций ~130р
              https://habrastorage.org/files/523/59c/36d/52359c36d1344a1c831f86ce7a8ed7e5.PNG

              В последнем случае цена — это стоимость «голой» машины без диска и без IP.
              То есть, в вашем случае
              Стоимость машины с тарифом «TINY»: 1х129.60 рублей
              +
              SSD диск 5Gb: 5х18.72 рублей
              +
              выделенный IP-адрес: 1х72 рубля
              =
              Итого: 295.20 рублей
                0
                В последнем случае цена — это стоимость «голой» машины без диска и без IP.

                Я извиняюсь, но зачем? Часто берут машины без диска и ip?
                Такая подача на сайте действительно путает — по десяти местам стоимость размазана.

                  0
                  Это ценообразование. Мы часто сталкиваемся с вопросом о том, что и сколько стоит.
                  Кому нужно готовое решение, тот в панели просто жмёт «Создать VDS». Кто интересуется экономикой, тому всегда можно сходить в маны.
                    +1
                    Мы часто сталкиваемся с вопросом о том, что и сколько стоит.
                    Быть может, как раз, потому часто и спрашивают.
                    Или вот, например, невидно информации, что подразумевается под core.
                    Как-то разрозненно все — вроде бы и много написано, но того, что обычно интересует в первую очередь сразу и не найти, это отталкивает.
                    Без задних мыслей, если что. Просто субъективный взгляд со стороны. Успехов.
                      0
                      Мы уже готовим новый сайт. Структуру и наполнение переиначим и будет все читабельно и удобно.
                      Нельзя не согласиться, что на сайте много лишнего на самых видных местах и нет нужного там, где это действительно нужно.
                      Может, ещё что будет из замечаний? Свежий взгляд для нас очень важен.

        Only users with full accounts can post comments. Log in, please.