Удалённый доступ к графике в Linux: от X11 до Docker с GPU

Привет Хабр! С вами снова ServerFlow, и сегодня мы хотим поговорить об удалённом доступе к графическим приложениям на Linux-серверах. Тема эта стала особенно актуальной в последнее время — всё больше задач требует работы с GPU на удалённых машинах. Будь то рендеринг в Blender на мощном сервере, работа с нейросетями или даже облачный гейминг.
X11 Forwarding: начинаем с простого
В нашей практике мы часто сталкиваемся с ситуациями, когда клиентам нужен доступ к графическому интерфейсу на сервере. Кому-то нужна GPU-ферма для рендеринга 3D графики, другим важно тестировать свои приложения в изолированной среде. А некоторые просто хотят организовать себе удалённое рабочее место с графическим ускорением. Сегодня разберём три популярных способа организации такого доступа — от простого к сложному.
Самый простой способ запустить графическое приложение с сервера — это X11 forwarding через SSH. Для этого даже не нужно ничего дополнительно устанавливать на сервер, достаточно базового пакета openssh.
Подключаемся к серверу командой:
ssh -X -p 47645 serverflow@IP_SSH_Сервера
Флаг -X включает проброс X11. Теперь можно запустить, например, Firefox прямо из консоли:
firefox &

Firefox проброшенный через SSH с открытым Хабром
На первый взгляд всё работает, но… При запуске браузера сразу становятся заметны подтормаживания интерфейса. Не говоря о том что интерфейс и пользовательский ввод отображаются на экране с очень заметной задержкой измеряемой даже не секундами, а порой минутами. С чем это связано? X11 forwarding работает напрямую с примитивами отрисовки, передавая по сети каждое действие с окном, почти никак не оптимизируя и не сжимая передаваемые данные, которых при передаче видео весьма не мало. На медленном соединении это создаёт заметные задержки.
Даже простая прокрутка веб-страницы генерирует тысячи X11-команд, которые должны быть переданы по сети. А теперь представьте, что происходит при воспроизведении видео или работе с 3D-графикой. Протокол просто не был рассчитан на такие сценарии использования.
Впрочем, винить за это мы X11 не будем, так как создавался он ещё в 1980х, для куда более простых графических интерфейсов, в куда меньших разрешениях и предназначался в основном для академической и корпоративной работы с мэинфреймами.
RDP в Docker: серьёзный подход

Примерная архитектура протокола RDP
Перейдём к более современному решению — RDP-серверу в контейнере Docker с поддержкой GPU. В отличие от X11, протокол RDP изначально проектировался для работы с удалённым рабочим столом через сеть. Он использует умные алгоритмы сжатия, кэширование элементов интерфейса и различные оптимизации специально для графики.
Например, при прокрутке веб-страницы RDP не передаёт каждый кадр целиком. Вместо этого он может передать команду «возьми область экрана X и сдвинь её на Y пикселей вниз», а затем досылает только изменившуюся часть. При воспроизведении видео включаются специальные алгоритмы сжатия, похожие на те, что используются в современных видеокодеках.
Но прежде чем мы запустим наш RDP-сервер, нужно правильно настроить поддержку GPU в Docker. Начнём с установки драйверов и тулкита:
sudo pacman -S nvidia nvidia-container-toolkit
После установки настраиваем Docker для работы с GPU:
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
Nvidia Container Toolkit добавляет специальный рантайм для Docker, который умеет правильно пробрасывать драйверы и библиотеки CUDA в контейнер. Без этого слоя графическое ускорение работать не будет, так как контейнер по умолчанию изолирован от железа.
Теперь самое интересное — запуск контейнера с RDP-сервером. Мы используем образ от LinuxServer.io с предустановленным XFCE. Создаём директорию для настроек и запускаем:
mkdir -p ~/rdesktop-data
docker run -d \
--name=rdesktop-arch-xfce \
--gpus "device=0" \
--runtime=nvidia \
--security-opt seccomp=unconfined \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Europe/Moscow \
-p 3389:3389 \
-v ~/rdesktop-data:/config \
--device /dev/dri:/dev/dri \
--shm-size="1gb" \
--restart unless-stopped \
lscr.io/linuxserver/rdesktop:arch-xfce
Разберем каждый параметр, потому что от их правильной настройки зависит стабильность работы нашего удаленного рабочего стола:
Флаг -d запускает контейнер в фоновом режиме. Это значит, что даже если мы закроем терминал, контейнер продолжит работать. Удобно для долгоживущих сервисов вроде нашего RDP-сервера.
--name=rdesktop-arch-xfce даёт контейнеру понятное имя. Потом будет проще найти его в списке контейнеров или перезапустить при необходимости.
Следующие два параметра отвечают за работу с GPU:
--gpus «device=0» указывает какую именно видеокарту использовать, если их несколько
--runtime=nvidia включает специальный режим работы Docker для поддержки GPU
--security-opt seccomp=unconfined отключает некоторые ограничения безопасности. Обычно Docker сильно ограничивает что может делать приложение в контейнере, но для графического интерфейса и работы с GPU нам нужно больше свободы. Да, это немного снижает безопасность, но без этого параметра наш рабочий стол просто не запустится.
Далее идут переменные окружения:
PUID=1000 и PGID=1000 задают ID пользователя и группы внутри контейнера. Важно чтобы они совпадали с вашим пользователем на основной системе, иначе будут проблемы с правами доступа к файлам
TZ=Europe/Moscow устанавливает часовой пояс. Не забудьте поменять на свой, если живёте в другом регионе
-p 3389:3389 пробрасывает порт для RDP. Первое число — порт на вашей машине, второе — внутри контейнера. Можно изменить первое число если порт 3389 уже занят.
-v ~/rdesktop-data:/config монтирует директорию с настройками. Все изменения в рабочем столе будут сохраняться на вашем диске и переживут перезапуск контейнера. Очень удобно для бэкапов.
--device /dev/dri:/dev/dri даёт контейнеру прямой доступ к графическому оборудованию для аппаратного ускорения. Без этого параметра графика будет работать только через программный рендеринг.
--shm-size=»1gb» увеличивает размер разделяемой памяти. По умолчанию он всего 64 мегабайта, чего явно мало для современных браузеров и других приложений. Гигабайт обычно хватает с запасом.
--restart unless-stopped говорит Docker’у перезапускать контейнер если он вдруг упадёт или сервер перезагрузится. Единственное исключение — если вы сами остановили контейнер командой stop.
Наконец, lscr.io/linuxserver/rdesktop: arch-xfce указывает какой образ использовать. Мы берём готовый образ от команды LinuxServer.io, который построен на базе Arch Linux и использует лёгкий рабочий стол XFCE.
Клиент для RDP — Remmina
После запуска контейнера нам понадобится RDP-клиент для подключения. В Linux одним из лучших вариантов является Remmina — он поддерживает множество протоколов, включая RDP, и умеет работать через SSH-туннель.
Сначала создаем туннель до нашего сервера:
ssh -L 3389:127.0.0.1:3389 -p 47645 serverflow@IP_SSH_Сервера
Запускаем Remmina и создаём новое подключение. В появившемся окне настройки выставляем следующие параметры:
На вкладке «Basic»:
Name: любое удобное название
Protocol: RDP
Server: localhost:3389
Username: abc
Password: abc (это стандартные данные для образа, рекомендуем сменить после первого входа)

Интерфейс Remmina на вкладке Basic
Переходим на вкладку «SSH Tunnel» и включаем туннелирование:
Enable SSH tunnel: включаем
Custom: выбираем этот вариант
SSH Server: вводим наш адрес сервера — IP_SSH_Сервера:47645
Username: serverflow
Authentication: выбираем Password или SSH key, если используете ключи

Интерфейс Remmina на вкладке SSH Tunnel
После сохранения профиля можно подключаться. При первом подключении Remmina спросит про сертификат RDP — соглашаемся и сохраняем его. Через несколько секунд мы увидим рабочий стол XFCE.
В чём преимущество такой схемы подключения через SSH туннель? Во-первых, это безопасность — весь RDP трафик шифруется SSH. Во-вторых, нам не нужно открывать порт RDP наружу, что снижает риски взлома. Ну и в-третьих, SSH умеет сжимать трафик, что иногда помогает на медленных каналах связи.

Remmina с запущенным удалённым рабочим столом
Теперь проверим работает ли проброс GPU в контейнер, для начала обновим систему через консоль и установим nvtop–
sudo pacman -Syu
sudo pacman -S nvtop

Remmina с запущенным в консоли nvtop
И как можно заметить система видит установленную в сервер видеокарту от Nvidia, Tesla P100.
Чтобы точно проверить всё ли работает, воспользуемся простой утилитой для базового бенчмарка — glmark2. Для начала установим её –
sudo pacman -S glmark2
И запустим –
nvidia-smi --query-gpu=utilization.gpu,temperature.gpu,memory.used --format=csv -l 1 & glmark2 && kill $!

Remmina с запущенным в консоли бенчмарком glmark2 и nvtop для мониторинга
Как можно заметить, для рендеринга glmark2 использует видеокарту и нагружает её на ~4–5%.
Подводим итоги
В итоге у нас получился полноценный удаленный рабочий стол с поддержкой GPU, работающий в изолированном контейнере. Производительность на высоте — можно комфортно работать с браузером, запускать требовательные приложения и даже смотреть видео без задержек. Во многом это заслуга RDP протокола, который гораздо эффективнее X11 forwarding в плане передачи графики по сети.
Контейнеризация дает нам гибкость — можно быстро развернуть такой же рабочий стол на другом сервере или сделать бэкап всех настроек. А поддержка GPU открывает интересные возможности, например для рендеринга 3D графики или работы с нейросетями прямо через удалённый доступ.
Конечно, настройка получилась чуть сложнее чем простой проброс X11, зато результат того стоит. К тому же, разобравшись один раз, повторить процесс уже гораздо проще.
В следующей статье мы пойдём дальше и покажем, как на базе этого решения сделать свой домашний игровой стрим-сервис. Окажется, что облачный гейминг в духе GeForce Now — это не сложнее чем VDI по RDP. Нужно будет только добавить поддержку геймпадов и настроить передачу звука. Так что оставайтесь на связи!
А пока делитесь в комментариях — как вы решаете задачу удаленного доступа к графическим приложениям? Может быть есть интересные кейсы использования GPU на удаленных серверах? До встречи в следующей статье!
