29 апреля, 2016

Ещё немного про SteelSeries engine в OS X

Проблемой, описанной в прошлом посте, дело не кончилось. После очередного обновления engine client опять отказался стартовать, показывая вечный Loading. На этот раз инсталлятор почему-то решил, что права на запись в ~/Library/Application Support/steelseries-engine-3-client/ мне не очень-то и нужны. Лечится, соответственно, рекурсивным добавлением соответствующих прав :)

25 февраля, 2016

Ломающий Steelseries Engine Client 3.6.6 в MacOS X

Не было печали - апдейтов накачали. В смысле, обновил SteelSeries Engine 3 для макоси до последней версии 3.6.6 и что-то пошло не так: сначала он начал утверждать, что мол де его неправильно запускают. Потом просто после запуска ничего не показывал, бесконечно изображая видимость бурной деятельности путем мигания точками около слова «LOADING» на всё своё окно.

Выйти-зайти, перезагрузиться и прочие стандартные шаманства не возымели эффекта, равно как и переустановка злополучной софтины с предварительным выпиливанием родным uninstaller'ом.

Как оказалось, uninstaller халтурит, и удаляет далеко не все продукты жизнедеятельности сносимого приложения.

В общем, исправить проблему можно так: даёте uninstaller'у сделать своё дело, затем переходите в ~/Library/Application Support и удаляете папку SteelSeries Engine 3 Client, затем заново устанавливаете SteelSeries Engine.

После этого всё начинает работать, а новую папку для себя оно вовсе с другим именем создает - steelseries-engine-3-client

30 сентября, 2015

Mikrotik WAN Failover и «повисающий» SIP

Не так давно довелось столкнуться с довольно тривиальной ситуацией, решений для которой в рунете сходу найти, к моему удивлению, не удалось. Ситуация: есть офис, раздачей интернетов в котором занимается роутер под управлением RouterOS. В офисе есть некоторое количество SIP-телефонов, а также резервный канал доступа в интернет на случай проблем с основным.

И вроде ничего не предвещало, как говорится, но вылез один очень неприятный момент: при переключении на резервный канал те самые SIP-телефоны напрочь отказывались заново устанавливать соединение с PBX (находящейся за пределами офиса), что заставляло обитателей того офиса вручную перезагружать каждый телефон, да еще и стараться побыстрее заметить, что телефоны отвалились, чтоб не пропустить какой-нибудь важный звонок.

В чем же дело? Всё довольно просто: переключение на резервный канал было реализовано с помощью двух маршрутов с destination 0.0.0.0/0 и разным значением distance в зависимости от приоритета, а также с настроенными соответствующим образом опциями check-gateway. Но переключение переключением, а вот таблицу NAT роутер при этом не очищал, т.к. его об этом никто и не просил, что приводило к «повисанию» UDP-соединений на том дефолте, который по несчастью отвалился.

Избежать подобной ситуации можно несколькими способами (мне попадались даже разного рода скриптовые ады, отключающие-включающие connection tracking, и прочие душераздирающие зрелища), но лично я решил сделать решение без ненужного скриптинга, максимально простое и лаконичное.

Итак, из руководства к RouterOS нам известно, что check-gateway проверяет доступность шлюза каждые 10 секунд, и если ответа на проверочный запрос не поступило в течение 10 секунд, то проверка считается неудачной. А после двух таких таймаутов подряд шлюз помечается как недоступный, тут-то и происходит переключение на резерв.

Для простоты будем считать, что период переключения каналов у нас равен 30 секундам. Тогда создадим netwatch, который будет пингать нашу телефонную станцию с интервалом в 15 секунд (половина от тех 30, чтоб наверняка). И да, разумеется наша телефонная станция должна в нормальной ситуации отвечать на пинг:

/tool netwatch add host=адрес_нашей_PBX interval=15s timeout=1s up-script="/ip firewall connection remove [find where dst-address~\"5060\"]" down-script="/ip firewall connection remove [find where dst-address~\"5060\"]"

Теперь, если вдруг у нас пропадет пинг до IP-АТС, из таблицы соединений будет вычищено всё, что шло на порт 5060. Но вообще я бы рекомендовал написать в up/down скрипт что-нибудь такое:

/ip firewall connection remove [find where src-address~"192.168.88."]

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

Не стоит беспокоиться насчет того, что такое решение может рвать соединения излишне часто: netwatch-скрипты выполняются по одному разу (единожды, когда пингуемый хост стал виден, и точно так же единожды, когда из виду пропал), а не циклично.
Данное решение было всячески оттестировано с пристрастием (и без), и никаких проблем выявлено не было.

14 июня, 2015

Еще одна возможная причина возникновения ошибок «Uncorrectable parity/CRC error» и борьба с ней

В интернете распространено мнение о том, что если у вас внезапно в /var/log/messages обнаружилась огромная куча ошибок вида
(ada2:ahcich2:0:0:0): WRITE_FPDMA_QUEUED. ACB: 61 00 f0 2f 9b 40 38 00 00 01 00 00
(ada2:ahcich2:0:0:0): CAM status: Uncorrectable parity/CRC error
(ada2:ahcich2:0:0:0): Retrying command
то это у вас либо диск умирает, либо питальник, либо шлейф от контроллера к диску сделан из дешевой китайской лапши. Однако, довелось столкнуться с ситуацией, когда на одном из двух абсолютно новых WD Purple у меня сразу полезло это безобразие, хотя диски явно были в порядке, шлейфы – тоже, а значения выходного напряжения у блока питания гуляли в пределах 0.03В, т.е. всё было вроде бы идеально, но ошибки продолжали сыпаться.

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

20 мая, 2015

«Два конца»

Интересная и не совсем тривиальная задача подвернулась. Ситуация: в некоторой организации локальная сеть имеет адрес, скажем, 192.168.0.0/24, и нужно пробросить некоторый порт (пусть будет tcp/8080) для кофеварки, адрес которой 192.168.0.50.

Казалось бы, что в этом такого? Так уж сложилось, что у провайдера, через которого мы получаем доступ в интернет по L2TP, адрес VPN-сервера тоже 192.168.0.50. Соответственно, сделать так, чтобы роутер подключался к VPN через сеть провайдера, у нас есть маршрут для 192.168.0.50/32, но если попытаться пробросить порт на этот адрес, пакеты закономерно пойдут на VPN-сервер провайдера, а не на нашу кофеварку, что нам совсем не нужно.

Что же делать?

Сначала создадим маршрут:

/ip route add dst-address=192.168.0.50/32 gateway=bridge-local routing_mark=coffee_mark

Обратите внимание на routing_mark.
Теперь создадим правило в mangle:
/ip firewall mangle add chain=prerouting in-interface=WAN protocol=tcp dst-port=8080 \
action=mark-routing new-routing-mark=coffee_mark

Ну и dst-nat правило, с ним ничего необычного:
/ip firewall nat add chain=dstnat in-interface=WAN protocol=tcp dst-port=8080 action=dst-nat to-addresses=192.168.0.50

Вот и все. Теперь соединениям, идущим на нашу кофеварку по указанному порту, будет присваиваться routing_mark, с помощью которого наш роутер поймет, где искать ту кофеварку.

07 апреля, 2015

Организация a/b тестирования веб-проектов средставами nginx

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

Определимся с условиями нашей задачи:
  1.  На новую версию сервиса мы должны направить ≈20% всех пользователей
  2.  Есть ip-адреса клиентов, которые должны всенепременно попадать на старую версию
  3.  То же самое, но – всенепременно на новую.
Далее я приведу конфигурационный файл целиком, а после опишу принцип его действия.

geo $mytest_participant {
  default              1;
  10.1.1.1             0;
  192.168.45.2         2;
}

map $mytest_participant $resulting_upstream_name {
  0     "old_version_upstream";
  1     "distributed_upstream";
  2     "new_version_upstream";
}

upstream distributed_upstream {
  ip_hash;
  server 1.1.1.1 weight=8; # Old version server, 80% of clients
  server 2.2.2.2; # New version server
}

upstream old_version_upstream {
  server 1.1.1.1;
}

upstream new_version_upstream {
  server 2.2.2.2;
}

server {
  listen 80;

  server_name example.com;

  location / {
    proxy_pass http://$resulting_upstream_name;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
  }

}

Итак, первый блок (geo) описывает как раз распределение пользователей по апстримам.
ip-адреса (можно использовать CIDR-нотацию, разумеется), для которых выставлено значение 0, будут направляться на старую версию принудительно, 1 - распределяться по вышеуказанному алгоритму (80% – на старую, 20% – на новую версию), ну и 2 - принудительно на новую.

Именно в зависимости от этого значения и присваивается имя апстрима в следующем блоке (map).

Далее все просто: описываются апстримы для старой и новых версий, а также наш распределенный апстрим. Для последнего используется алгоритм балансировки ip_hash, благодаря которому одни и те же ip-адреса будут попадать строго на тот апстрим, на который попали впервые. Если нам нужно изменить процентное соотношение распределения по старой и новым версиям, то правим соответствующим образом параметр weight для старого апстрима.

Вот, собственно, и всё.

Нужно, однако, помнить, что ip_hash использует только первые три октета ip-адреса клиента, т.е. на ту или иную версию попадет сразу весь /24 сегмент. Однако обычно это не является чем-то критичным, тем более, что в блоке geo можно подкорректировать ситуацию для отдельных ip-адресов, при наличии необходимости.

28 октября, 2014

Ubuntu - обновленные библиотеки libicu с последними изменениями временных зон

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

Наши разработчики используют Symfony2, и кое-где версия фреймворка ниже 2.6, а значит Symfony forms использует icu, и обновлять последний как-то мало кто торопится.
Поводом для написсания данного поста стал пост об этой же проблеме на хабре, но там рассматривалось решение проблемы для CentOS.

Мы используем преимущественно ubuntu, поэтому я сделал PPA с обновленными библиотеками (для 12.04 и 14.04).

Возьмем тестовый скрипт из той статьи на хабре и проверим на Ubuntu с «родным» icu:
<?php
$dateIn = '27.10.2014';
$tz = 'Europe/Moscow';
date_default_timezone_set($tz);
$intlDateFormatter = new \IntlDateFormatter('ru_RU', 2, -1, $tz, 1, 'dd.MM.yyyy');
$timestamp = $intlDateFormatter->parse($dateIn);

var_dump($intlDateFormatter->format($timestamp));
var_dump(date('d.m.Y', $timestamp));
И выполним его:
php ./icutz.php 
string(10) "27.10.2014"
string(10) "26.10.2014"
Всё плохо. Теперь подключим ppa и обновим библиотеку из него:
sudo add-apt-repository -y ppa:rzz/icu && sudo aptitude update
Обновляем libicu
Для Ubuntu 12.04: sudo aptitude safe-upgrade libicu48
Для Ubuntu 14.04: sudo aptitude safe-upgrade libicu52

Снова запускаем наш скрипт:
php ./icutz.php 
string(10) "27.10.2014"
string(10) "27.10.2014"
Готово! Но вообще, конечно, я рекомендую помимо всего прочего обновить версию Symfony, чтобы отвязаться от icu насовсем.

UPD, 12 Ноября 2014: в связи с поступившими просьбами сделать обновленный пакет icu 5.2 для 12.04, icu 4.8 переехал в отдельный PPA: ppa:rzz/icu48