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

На работе среди прочего я поддерживаю кластер из реплик серверов БД, работающих на MongoDB, и иногда приходится поднимать новые реплики. Понаблюдав за процессом инициализации реплики, я увидел, что зачастую он упирается в скорость 100-мегабитного подключения к серверу. В передающихся данных имеется значительная избыточность (например повтояющиеся названия полей документов), а значит, можно завести какое-нибудь сжатие. Но ведь в протоколе mongo такого не предусмотрено? Будем решать проблему внешними инструментами.

В первую очередь возникла мысль поднять какую-нибудь разновидность VPN со сжатием вроде OpenVPN или более стандартного ppp. Однако установка и настройка этих решений — тот еще геморрой, поэтому был найден простой как пробка пакет vtun. Копипастим конфиги из прилагающихся в пакете примеров, немножко правим по вкусу и тоннель точка-точка между двумя серверами работает. Теперь надо измерить скорость внутри тоннеля, для этого воспользуемся утилитой iperf, которая генерирует поток данных с одной стороны и принимает поток с другой, попутно измеряя скорость. На потоке из /dev/zero при физическом соединении 100 мегабит iperf мне выдал целых 200 мегабит. При этом предел скорости определялся скоростью процессора, сжимающего данные при отправке. Относительный выигрыш может быть даже больше, если данные передаются через более медленный линк, например wifi или интернет, или если у вас мощный и ничем не занятый процессор.

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

Хвала Линусу, в его операционке есть понятие условной маршрутизации. Маршруты в линуксе собираются в отдельные независимые таблицы, имена которых настраиваются в файле /etc/iproute2/rt_tables. Туда нам нужно добавить новое имя, дав ему уникальный номер. К примеру, я назвал таблицу “tun” по имени интерфейса, в который будет идти маршрут, и задал номер 200.

Теперь нужно добавить собственно сам маршрут командой:

ip ro add <адрес удаленной машины> dev <название интерфейса тоннеля> table tun

Система самостоятельно не будет использовать маршруты в таблицах, отличающихся от системных, поэтому добавим правило, которое будет некоторый (не весь!) трафик отправлять в новую таблицу:

ip ru add fwmark <число> table tun

Число в этой команде — метка, которую пакетам присваивает системный фаервол. По умолчанию пакеты не метятся, поэтому iptables тоже следует соответствующим образом настроить, добавив правило:

iptables -t mangle -I OUTPUT <условия, по которым маркировать пакеты> -j MARK --set-mark <число>

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

Но несмотря на отличные показатели tcpdump, вы еще не получите свой трафик в приложение, потому что linux отбрасывает пакеты, которые пришли на интерфейс, у которого нет соответствующего адреса. Для изменения этого поведения нужно выключить параметр rp_filter:

sysctl net.ipv4.conf.<название интерфейса тоннеля>.rp_filter=0

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

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

Конечно, не стоит сразу делать такие вещи на серверах, работающих в production-режиме, сначала следует потренироваться на кошках и потестировать на локалхосте.

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