Почему с ростом трафика процессор сервера загружается почти на 100%, время ответа растёт, а контейнеры приложения почти не потребляют CPU?
Дополнительную нагрузку может создавать docker-proxy – процесс, который Docker использует для перенаправления подключений с опубликованных портов хоста в контейнеры. Если через него проходит интенсивный трафик, дополнительные операции по приёму соединений и передаче данных могут потреблять значительную часть CPU
Docker позволяет отключить этот прокси и обрабатывать соответствующие соединения средствами сетевого стека Linux. Разберём, как определить, что docker-proxy действительно ограничивает производительность, изменить конфигурацию и проверить результат
Как docker-proxy расходует CPU
Когда контейнер запускается с параметром -p 8080:80, Docker публикует его порт 80 на порту 8080 хоста. При этом доставлять трафик в контейнер можно двумя способами
Первый – через NAT. Docker создаёт правила перенаправления, а ядро Linux изменяет адрес назначения пакетов и передаёт их контейнеру. Для такого соединения отдельный процесс в пространстве пользователя не требуется
Второй способ предполагает использование docker-proxy. Он принимает TCP-соединение на стороне хоста, открывает ещё одно соединение с контейнером и пересылает данные между ними
Это видно в исходном коде docker-proxy. Для подключения к контейнеру используется net.DialTCP, а двусторонняя передача данных выполняется через io.Copy
В результате обработка соединения требует дополнительных операций чтения и записи, переключений между пространством ядра и пользовательским процессом, а также памяти для обслуживания соединений
При небольшом количестве запросов такие затраты могут быть практически незаметны. Но с ростом числа подключений и объёма передаваемых данных увеличивается и работа прокси. Если процесс начинает занимать существенную часть CPU, он способен ограничивать пропускную способность сервиса, даже когда само приложение ещё может обрабатывать больше запросов
При этом запущенный docker-proxy не означает, что весь трафик опубликованного порта проходит через него. В обычных bridge-сетях внешние IPv4-подключения часто обрабатываются через NAT. Прокси может использоваться для локальных подключений с хоста, обращений между контейнерами через опубликованные порты и некоторых других сетевых сценариев
Поэтому отключение docker-proxy имеет смысл рассматривать прежде всего тогда, когда измерения показывают, что он действительно участвует в обработке нагрузки и потребляет заметные ресурсы
Как выявить нагрузку от прокси
Если пропускная способность сервиса перестала расти, а время ответа продолжает увеличиваться, начните с анализа потребления CPU. Важно понять, какие именно процессы загружают сервер
Для проверки docker-proxy можно использовать pidstat из пакета sysstat
pidstat -u -r -C docker-proxy 1 10
Команда собирает десять отчётов с интервалом в одну секунду. В них отображаются загрузка CPU и потребление памяти процессами docker-proxy
Основное внимание стоит уделить %CPU. Если один или несколько прокси интенсивно используют процессор во время роста нагрузки, нужно выяснить, какие опубликованные порты они обслуживают
Проверить публикации контейнеров можно через Docker
docker ps --format 'table {{.Names}}\t{{.Ports}}'
Допустим, приложение использует публикацию 8080:80. Посмотрим на активные TCP-соединения с портом 8080
sudo ss -ntp state established '( sport = :8080 or dport = :8080 )'
Если в выводе есть соединения, связанные с docker-proxy, команда покажет соответствующий PID. По нему можно сопоставить активные подключения с процессом, который расходует CPU
Поскольку короткие соединения могут закрываться за миллисекунды, проверку лучше выполнять при непрерывном потоке запросов. Иначе ss может не успеть показать их
Не менее важно учитывать маршрут трафика. Например, запрос, поступивший непосредственно на IPv4-адрес сервера, и запрос от локального nginx к 127.0.0.1 могут попасть в контейнер разными путями. Поэтому проверять нужно именно ту схему подключения, которая используется под рабочей нагрузкой
Если docker-proxy потребляет значительную часть процессорного времени, а при увеличении числа запросов растут задержки или ошибки, есть основания проверить его влияние на производительность
Перед изменением конфигурации сохраните исходные показатели. Помимо CPU самого прокси, желательно зафиксировать общую загрузку сервера, потребление памяти, RPS, задержки p95/p99 и количество ошибок
Если для мониторинга используется Grafana, необходимые показатели можно вывести на отдельные панели. О том, как управлять такими дашбордами через JSON и AI-агентов, мы рассказывали в другой статье
Доступ к портам без прокси
Параметр userland-proxy: false запрещает Docker использовать отдельный пользовательский прокси для публикации портов. В стандартной bridge-сети обработка соответствующих соединений будет опираться на правила NAT и возможности сетевого стека Linux
Это позволяет исключить из пути передачи данных дополнительный процесс и связанные с ним затраты CPU
Однако у двух способов перенаправления есть различия, которые необходимо учитывать
В первую очередь проверьте соединения с самого хоста и из других контейнеров через опубликованные порты. Их поведение зависит от настроек сети и маршрутизации
Отдельного внимания требует IPv6. Если контейнер находится в сети только с IPv4, а порт опубликован без указания IP-адреса хоста, включённый docker-proxy может принимать подключения по IPv6 и перенаправлять их на IPv4-адрес контейнера
После отключения прокси такой способ доступа перестанет работать. Если приложение должно принимать подключения по IPv6, потребуется настроить сеть соответствующим образом или использовать другой способ перенаправления. Это ограничение описано в документации Docker
Также не следует одновременно отключать iptables или ip6tables. Эти настройки отвечают за создание правил сетевой фильтрации и перенаправления пакетов. Их изменение может нарушить работу контейнерных сетей, о чём Docker предупреждает в документации по firewall
Как отключить docker-proxy
Настройка userland-proxy относится ко всему Docker Engine и задаётся на уровне демона dockerd
В обычной установке Linux конфигурация находится в /etc/docker/daemon.json. Если путь был изменён, его можно найти среди аргументов запущенного демона
ps -C dockerd -o args=
При наличии --config-file Docker использует указанный в нём файл
Сохраните резервную копию действующей конфигурации с правами доступа, затем добавьте "userland-proxy": false, не изменяя остальные параметры
Если daemon.json ещё не существует, создайте его со следующим содержимым
{
"userland-proxy": false
}
Также проверьте аргументы запуска службы
systemctl cat docker
Если там уже присутствует --userland-proxy, оставьте настройку только в одном месте. Docker не допускает дублирования параметров в командной строке и конфигурационном файле, даже когда их значения совпадают
После ручного изменения unit-файла или drop-in обновите конфигурацию systemd
sudo systemctl daemon-reload
Перед перезапуском Docker проверьте корректность JSON-конфигурации
sudo dockerd --validate --config-file /etc/docker/daemon.json
Если файл расположен в другом каталоге, укажите фактический путь
При успешной проверке команда вернёт configuration OK и завершится с кодом 0. Учтите, что проверка самого файла не исключает конфликтов с параметрами запуска службы
Теперь перезапустите Docker
sudo systemctl restart docker
sudo systemctl is-active docker
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
Проверьте, что служба находится в состоянии active, а необходимые контейнеры запущены
После этого посмотрите, остались ли процессы docker-proxy
pgrep -a -x docker-proxy
Если команда ничего не вывела, таких процессов больше нет. Если они остались, проверьте, какие публикации портов продолжают их использовать
При включённом live-restore контейнеры могут работать во время перезапуска демона, сохраняя прежние сетевые настройки. Поэтому одного перезапуска Docker не всегда достаточно для обновления существующих публикаций
Подробнее особенности такого режима описаны в документации Live Restore
Проверка доступности и нагрузки
После отключения прокси сначала убедитесь, что приложения остались доступны по всем необходимым адресам
Проверьте подключения извне, обращения с самого хоста и взаимодействие между контейнерами. Если используется IPv6, проверьте его отдельно. Для сервисов, работающих одновременно по TCP и UDP, необходимо проверить оба протокола
Также убедитесь, что прежние ограничения доступа сохранились. Например, порт, опубликованный только на 127.0.0.1, должен оставаться недоступным с других машин
Когда работоспособность подтверждена, повторите нагрузочное тестирование
Чтобы сравнение было корректным, используйте те же адреса и маршруты подключения, интенсивность запросов, длительность теста и время прогрева приложения. Иначе изменение показателей может быть связано не с отключением прокси, а с другими условиями
Сравните результаты по нескольким направлениям
- CPU – насколько изменилась загрузка процессора сервера
- Память – сколько ресурсов освободилось после удаления процессов прокси
- Пропускная способность – увеличилось ли количество запросов, которое сервис способен обрабатывать без деградации
- Задержки – изменились ли p95 и p99 при одинаковом RPS
- Ошибки – сохранилась ли стабильность обработки запросов
Особенно показательно сравнение при одной и той же интенсивности нагрузки. Например, если до отключения прокси CPU был загружен значительно сильнее, а после изменения при прежнем RPS освободилась часть процессорных ресурсов, можно говорить о снижении накладных расходов на передачу трафика
При этом уменьшение загрузки CPU не обязательно приведёт к пропорциональному росту RPS. После устранения одного ограничения производительность может упереться в базу данных, вычисления приложения, сетевой интерфейс или другой компонент
Нужно учитывать и влияние перезапуска контейнеров. Он может сбросить кеши, соединения с базами данных и другие внутренние состояния приложения. Если требуется точнее установить причину изменений, полезно повторить измерения с исходной настройкой в одинаковых условиях
Как вернуть настройки Docker
Если после отключения прокси появились проблемы с доступностью сервисов, верните исходный daemon.json из резервной копии
Если файл был создан только для изменения userland-proxy и других настроек в нём нет, его можно удалить
Восстановите также аргументы запуска демона, если они изменялись. После ручной правки unit-файла или drop-in перечитайте конфигурацию systemd
sudo systemctl daemon-reload
Если после отката файл daemon.json существует, проверьте его через dockerd --validate --config-file, указав его путь. Затем перезапустите Docker
Для контейнеров, у которых сохранились прежние публикации портов, обновите их тем же способом, что использовался при отключении прокси. После этого проверьте доступность приложений
Если Docker не запускается, посмотрите журнал службы
sudo journalctl -u docker -n 100 --no-pager
Сообщения об ошибках помогут определить причину, например некорректный JSON или дублирование параметров запуска
Отключение docker-proxy не является универсальным способом ускорить Docker. Но когда нагрузка действительно проходит через этот процесс и он занимает существенную часть CPU, переход на обработку соединений средствами ядра Linux способен убрать дополнительное ограничение производительности без изменений в коде приложения
