Сказ о sysctl’ях (народная пингвинская история)

Очень часто администраторы выполняют настройку системы просто настройкой базовых вещей — ip, dns, hostname, поставить софт, а все остальное уже настройки приложений. В большинстве случаев так оно и есть, поскольку в linux«е очень разумные умолчания и, в большинстве случаев, этих умолчаний хватает и все живут счастливо. Среди совсем начинающих ходят легенды о неких sysctl«ях, а те, кто поопытнее видели и даже чего-то правили.

Но приходит момент, когда админ в своих походах по системе встречает этого зверя — sysctl. Вероятнее всего он встречает кого-то из семейства net.ipv4 или vm, даже вероятнее всего net.ipv4.ip_forward, если поход за роутером или vm.swappinness, если он обеспокоен подросшим swap«ом своего пингвина. Первый зверь разрешает пингвину принимать пакеты одним крылом и отдавать другим (разрешает маршрутизацию), а второй помогает справиться с использованием swap«а в спокойной системе и регулировать его использование — в нагруженной.

59ef60ae73130650626622.jpeg

Встреча с любым из этих «зверей» открывает ворота в целый мир настроек системы и компонентов ядра, существует несколько семейств, названия большинства которых говорят сами за себя: net — сеть; kern — ядро; vm — память и кэши. Но это всё «сказка», реальность гораздо интереснее.

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

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

Вот, к примеру, параметры net.ipv4.tcp_mem, rmem, wmem — выглядят очень похоже, три числа типа »4096 87380 6291456», только, вот незадача, для [rw]mem это байты, а для mem — страницы и, если задать всем трём параметрам, одинаковое значение, то будет «взрывоопасная» конфигурация, т.к. tcp_mem отвечает за управление памятью потребляемой tcp, а [rw]mem за буфера сокета.

А бывают sysctl«и которые, пока не встретишь и не огребёшь — не задумаешься о чем этот sysctl, как, к примеру, net.ipv4.conf.all.rp_filter. Злейшая штука, которая стреляет только тогда, когда у вас возникает необходимость делать ассиметричные маршруты, то есть, у вашего маршрутизатора 2+ интерфейса и трафик может прийти с одного интерфейса, а вернуться через другой, то есть достаточно редко. Называется Reverse Path Filtering, блокирует пакеты которые приходят с адреса, который не маршрутизируется через интерфейс откуда они приходят.
Бывают параметры очень полезные, но требующие вдумчивого изучения документации и расчетов, чтобы выяснить, как они вам могут помочь в вашей конкретной ситуации. Повторюсь, что умолчания в linux«е, достаточно хорошие. В особенности это касается настроек tcp и сети в целом.

Параметры же которыми приходится играться достаточно часто это:

vm.swappiness — настройка агрессивности «высвапливания» памяти. Это нужно для того, чтобы поддерживать как можно больший объем оперативной памяти доступным для приложений и работы. В значении по умолчанию — 60 система переводит в swap те страницы памяти, которые не использовались в течении какого-то продолжительного времени, в значении 0 или 1 — система старается использовать своп только когда не может аллоцировать физическую память или когда объем доступной памяти подходит к указанному в vm.min_free_kbytes. Нельзя сказать, что использование свопа однозначно хорошо или плохо. Все зависит от ситуации и профиля использования памяти, а эта ручка позволяет нам управлять отношением системы к свопу от 0 — совсем не нравится, до 100 — о да, своооп!

vm.min_free_kbytes  — (раз уж упомянут в vm.swappiness) Определяет минимальный размер свободной памяти который необходимо поддерживать. Если установить слишком маленьким — система сломается, если слишком большим — будет часто приходить OOM (Out Of Memory) killer.

vm.overcommit_memory — разрешает/запрещает «аллоцировать» памяти больше чем есть: 0 — система каждый раз проверяет есть ли достаточное количество свободной памяти; 1 — система всегда полагает, что память есть и разрешает аллокацию пока память действительно есть; 2 — запретить просить памяти больше чем есть — аллоцировать можно не больше чем RAM + SWAP. Это может выстрелить когда у вас есть приложение, например redis, которое потребляет > ½ памяти и решает записать на диск данные, для чего форкается и копирует все данные, но т.к доступной памяти может не хватить — может либо сломаться запись на диск, либо прийти OOM и убить что-то нужное.

net.ipv4.ip_forward — разрешение или запрет на маршрутизацию пакетов. С необходимостью покрутить эту ручку мы сталкиваемся когда настраиваем маршрутизатор. Тут все более-менее понятно: 0 — выключить; 1 — включить.

net.ipv4.{all, default, interface}.rp_filter — контролирует опцию Reverse Path Filtering: 0 — не проводить проверку, отключить; 1 — «строгий» режим, когда отбрасываются пакеты ответы которым не уходили бы через тот интерфейс с которого пришел пакет; 2 — «расслабленный» режим — отбрасываются только те пакеты, маршрут до которых неизвестен (при наличии маршрута по умолчанию, по-моему, должно приводить к тому же эффекту, что и 0).

net.ipv4.ip_local_port_range — определяет минимальный и максимальный порты, который используется для создания локального клиентского сокета. Если у вас система совершает большое количество обращений к сетевым ресурсам, то у вас может возникнуть проблема нехватки локальных портов для установки соединений. Этот параметр позволяет регулировать диапазон портов, который используется для установки клиентских соединений. Так же это может быть полезно для того, чтобы обезопасить ваши сервисы которые «слушают» высокие порты.

net.ipv4.ip_default_ttl — время жизни пакета (TTL) по умолчанию. Может понадобиться когда надо ввести в заблуждение оператора СС пользуясь телефоном как модемом, либо когда надо убедиться, что пакеты от этого хоста не уходят за периметр сети.

net.core.netdev_max_backlog — регулирует размер очереди пакетов между сетевой картой и ядром. Если ядро не успевает обрабатывать пакеты и очередь переполняется, то новые пакеты отбрасываются. может потребоваться подкрутить в определенных ситуациях, чтобы справиться с пиковыми нагрузками и не испытывать сетевых проблем.

Ниже два параметра отвечающие за очереди соединений, то есть TCP. Эти два параметра в первую очередь подвергаются тюнингу на нагруженных web-сервисах, в случае когда возникают проблемы.

Для понимания надо знать как работают соединения в tcp:

  1. Программа открывает слушающий сокет: socket () → listen (). В итоге получает, например *:80 (80-й порт на всех интерфейсах).
  2. Клиент устанавливает соединение: a) посылает серверу syn-пакет (вот в этом месте и работает tcp_max_syn_backlog); b) получает от сервера syn-ack; c) посылает серверу ack (а вот тут уже работает somaxconn)
  3. Через вызов accept соединение обрабатывается и передается процессу для работы с конкретным клиентом.


net.core.somaxconn — размер очереди установленных соединений ожидающих обработки accept (). Если у нас возникают небольшие пики нагрузки на приложение — возможно вам поможет увеличение этого параметра.

net.ipv4.tcp_max_syn_backlog — размер очереди не установленных соединений.

Для большинства настроек TCP, да как, собственно, и всех остальных — требуется понимание работы механизмов, которые эти настройки регулируют.
Например как tcp передает данные. Поскольку этот протокол «гарантирует доставку», то ему требуются подтверждения о доставке от второй стороны соединения. Эти подтверждения называются ACKnoweledgement. Подтверждается полученный диапазон байт. Так же мы знаем, что данные в сеть мы можем передавать блоками равными размеру MTU (пусть, для простоты, 1500 байт), а передать нам надо больше, допустим 1500000 байт, то есть 1000 фреймов, данные будут фрагментированы. Если у нас сервера в одной сети на расстоянии одного патч-корда друг от друга — проблем то мы и не заметим, а вот если у нас идет обмен с удаленной системой, то дожидаться подтверждения каждого пакета — очень долго, ведь нам надо дождаться пока ТУДА дойдет наш пакет и оттуда вернется подтверждение о получении это будет очень сильно сказываться на скорости передачи данных. Чтобы решить этот вопрос было введено tcp_window, под размер которого выделено 16 бит в заголовке tcp. Грубо говоря, это количество байт, которые мы можем отправить без подтверждения. В 16ти битах мы можем хранить значение максимум 2^16=65536 (65Kb), что в наш век многогигабитных сетей вообще не много.

Чтобы понять, как мы можем передавать данные, допустим, из Москвы в Новосибирск (RTT (Round Trip Time) = 50ms) через 1Gbit канал давайте сделаем несколько расчетов (ниже ОЧЕНЬ грубые расчеты).

  1. Без tcp_window. Получается, что мы можем отправлять только 1500 байт в .05s, 1500/0.05 = 30000 байт/секунду. Маловато, при том, что канальная скорость у нас 1Gbit/s, что грубо примерно равно 100Mb/s. ~30Kb/s vs ~100Mb/s — есть проблема, мы не утилизируем доступную полосу.
  2. С tcp_window равным 65536 (максимум который можно указать в заголовке). Т.е. мы сразу можем отправить все наши 65К. 65536/0.05 = 1310720 = 1.25Mb/s. 1.25Mb/s vs ~100Mb/s — все равно маловато, разница примерно в 80 раз.
  3. Так сколько же нам надо для того, чтобы утилизировать хотя бы 900Mbit? проведем обратные вычисления. (900000000/8)b/s*0.05s=5625000b ~= 5.36 Mb. Это размер окна, который нам нужен, чтобы эффективно передавать данные. Но поскольку у нас очень длинный линк, то у нас там могут возникать проблемы => потери. Это тоже будет влиять на пропускную способность. Для того, чтобы через 16 битное поле получить возможность уведомлять о размере окна большем 65К была введена опция tcp_window_scaling.


net.ipv4.tcp_window_scaling — 0 — выключает масштабирование окна; 1 — включает масштабирование окна. Это должно поддерживаться с обеих сторон и служит для оптимизации использования полосы канала.

Но мало иметь возможность указать окно больше 65К, нужно еще иметь возможность держать все необходимые данные в памяти выделенной для сокета, в нашем случае в tcp_buffer«ах:

net.ipv4.tcp_wmem, net.ipv4.tcp_rmem — настройки Read и Write буферов выглядят одинаково. Это три числа в БАЙТАХ, «min default max» — минимальный гарантированный размер буфера, размер по умолчанию и максимальный размер, больше которого буферу система не даст вырасти. К настройке этих параметров стоит подходить с пониманием того, сколько у вас ожидается соединений, сколько данных вы собираетесь передавать и на сколько «медленные» ваши клиенты/сервера.

net.ipv4.tcp_mem — настройки управления памятью tcp-стека. 3 числа в СТРАНИЦАХ «min pressure max», которые описывают: min — порог использования памяти буферами ниже которого система не будет заботиться о вытеснении буферов; pressure — порог при достижении которого система будет заботиться о сокращении потребления памяти буферами, покуда это возможно; max — порог при достижении которого память выделяться не будет и буфера не смогут расти.

Иногда приложения падают, так сказать, «в корку». Эту корку («дамп памяти») можно использовать для отладки проблемы с помощью gdb. Вещь безусловно полезная, когда настроена. По умолчанию, coredump сохраняется в безликий файл core в рабочем каталоге приложения, возможно он будет еще сопровожден pid приложения, но можно сделать все гораздо удобнее:

kernel.core_pattern — позволяет задавать формат имени (и пути), которое будет использоваться для сохранения core dump. Например,»/var/core/%E.%t.%p» сохранит «корку» в директорию /var/core/ использовав в имени полный путь до программы, которая упала (заменив / на !) и добавив timestamp события и pid приложения. Можно даже перенаправить core во внешнюю программу для анализа. Подробнее можно узнать в man 5 core.

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

Удачи в консоли.

Вот такая небольшая заметочка, которую мы подготовили в рамках нашего курса «Администратор Linux». Тематику выбирали наши слушатели голосовалкой, так что надеемся, что заметка вам будет интересна и полезна.

© Habrahabr.ru