Запуск GUI-приложения в Docker

Запуск GUI-приложения в Docker

Хочу поведать историю о том, как я запустил GUI-приложение в Docker. некоторые спросят, зачем? Ну есть много причин, вот некоторые из них:

  • Нет нужного пакета/утилиты под твою ОС.
  • Проверить что получится.

Запуск GUI-приложения в Docker

Начну с теории и закончу примерами.

Архитектура X window system 101

В *NIX системах, приложение GUI имеет роль «X-клиента». Каждый раз, когда он перерисовывает свое содержимое, последовательность графических команд кодируется все в X протокол используя библиотеку (обычно Xlib) и передается в сокет X11. На другом конце, X-сервер считывает такие команды из сокета и отображает их на дисплей:

Архитектура X window system 101

Контейнерезированные (Containerizing) GUI приложения

Взглянув на архитектуру X window системы, ясно то, что для того, чтобы наши контейнеризованные графические приложения могли рисовать на экране, нам нужно предоставить ему доступ к сокету X11 (запись), и нам нужен X-сервер для использования и рендеринга графики команд на экран.

Мы можем подойти к этой проблеме с двух сторон:

  • Мы можем связать xvfb и VNC-сервер с нашим образом контейнера.
  • Мы можем совместно использовать сокет X11 хоста с контейнером в качестве внешнего раздела.

Контейнерезированные (Containerizing) GUI приложения

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

Совместное использование сокета X11 хоста с контейнером — это гораздо более модульное решение, которое позволяет использовать более компактный файл Docker, и он позволяет владельцу X-сервера переключать реализацию в соответствии с ее потребностями (возможно, X-сервер нашего хоста может быть даже самой xvfb).

Для начала, установим вспомогательное ПО:

Установка docker machine в Unix/Linux

Установка Docker на Debian/Ubuntu

Установка Docker на CentOS/RedHat/Fedora

Установка docker-compose в Unix/Linux

И так, сейчас я приведу несколько примеров использования docker чтобы запустить графическое ПО внутри докер-контейнера. Самый простой способ сделать это, это выполнить:

$ docker run --rm -it \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
your_image

Или:

$ docker run --rm -d \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
your_image

Или:

$ docker run --privileged -v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0 your_image

Это самые простые способы для запуска.

Поддержка 3D hardware acceleration:

$ docker run --rm -it \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
your_image

Или:

$ docker run --rm -d \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
your_image

Поддержка звука (Audio):

$ docker run --rm -it \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
--device /dev/snd \
your_image

Или:

$ docker run --rm -d \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
--device /dev/snd \
your_image

Поддержка Webcam:

$ docker run --rm -it \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
--device /dev/snd \
--device /dev/video0 \
your_image

Или:

$ docker run --rm -d \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
--device /dev/snd \
--device /dev/video0 \
your_image

Используем date/time как и на хосте:

$ docker run --rm -it \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
--device /dev/snd \
--device /dev/video0 \
-v /etc/localtime:/etc/localtime:ro \
your_image

PS: Некоторые дистрибутивы не используют /etc/localtime, чтобы установить часовой пояс, в этих случаях вам нужно будет проверить, как он это делает и «реплицировать» в контейнер.

Проброс конфигов в контейнер:

$ docker run [--rm [-it]|-d] \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
--device /dev/snd \
--device /dev/video0 \
-v /etc/localtime:/etc/localtime:ro \
-v $HOME/.config/app:/root/.config/app \
your_image

Подключаем Video-game controller:

$ docker run [--rm [-it]|-d] \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY \
--device /dev/dri \
--device /dev/snd \
--device /dev/video0 \
-v /etc/localtime:/etc/localtime:ro \
--device /dev/input \
your_image

Это были примеры запусков. Сейчас приведу наглядные примеры….

-=== Запуск mysql-workbench в Docker ===-

После чего, я создам машину для тестов, буду использовать docker-machine:

$ docker-machine create --driver virtualbox --virtualbox-cpu-count "2" --virtualbox-memory "2048" --virtualbox-disk-size "20000" mysql-workbench

Потом выполняю:

$ docker-machine env mysql-workbench

И:

$ eval $(docker-machine env mysql-workbench)

Машина создана, подключаемся к ней:

$ docker-machine ssh mysql-workbench

Внутри созданной докер-машины, создаем:

# vim Dockerfile

И прописываем:

FROM debian
RUN apt-get update
RUN apt-get install -qqy x11-apps mysql-workbench libcanberra-gtk-module packagekit-gtk3-module
ENV DISPLAY :0
CMD /usr/bin/mysql-workbench

Где нужно изменить «ENV DISPLAY :0» строку на свой «DISPLAY», чтобы узнать его, выполните:

$ echo $DISPLAY

Получите вывод (Если у вас MacOS X):

/private/tmp/com.apple.launchd.6iXsMbPJbh/org.macosforge.xquartz:0

Получите вывод (Если у вас Linux. Проверял на CentOS 6):

:0.0

Ну что, создаем контейнер:

$ docker build -t workbench - < Dockerfile

Запускаем контейнер и прокидываем в него сокет. Контейнер автоматически стартует MySQL-Workbench при запуске:

$ docker run --privileged -v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0 workbench

PS: Можно прописать переменную:

$ export XSOCK=/tmp/.X11-unix/X0

Потом запуск будет проходить вот так:

$ docker run --privileged -v $XSOCK:$XSOCK workbench

Честно сказать, у меня не получилось запустить данный контейнер на моем macbook, получил ошибку:

(mysql-workbench-bin:19): Gtk-WARNING **: cannot open display: unix/private/tmp/com.apple.launchd.6iXsMbPJbh/org.macosforge.xquartz:0

Дубль 2, пробую выполнить все тоже, но на CentOS 6:

[root@puppet-agent captain]# docker run --privileged -v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0 workbench
No protocol specified

(mysql-workbench-bin:20): Gtk-WARNING **: cannot open display: :0
[root@puppet-agent captain]# xhost +local:root
non-network local connections being added to access control list
[root@puppet-agent captain]# xhost +local:captain
non-network local connections being added to access control list

ИЛИ, для всех юзеров:

# xhost +localhost

Но это не верное решение, берем свой eth0 ИП:

# xhost + $ip

Так же, пробросим переменную для дисплея:

# export DISPLAY=:0.0

После этого, запускаю:

[root@puppet-agent captain]# docker run --privileged -v /tmp/.X11-unix/X0:/tmp/.X11-unix/X0 workbench

Вылезли другие ошибки… Посидел пару часов, я исправил ошибки на маке (описание решения, в самом низу статьи), ну а сейчас — я запускаю контейнер:

$ docker run -it -e DISPLAY=$ip:0 -v /tmp/.X11-unix:/tmp/.X11-uni workbench

Или (Но 1-й вариант лучше как по мне):

$ docker run --privileged --name firefox -e DISPLAY=$ip:0 -v /tmp/.X11-unix:/tmp/.X11-unix workbench

Получаю:

Запуск mysql-workbench в Docker

Шикарно! Все заработало!

-=== Запуск FireFox в Docker ===-

Существует несколько различных вариантов запуска GUI-приложений внутри Docker контейнера:

  • С использованием SSH с пересылкой X11
  • С использованием SSH с пересылкой VNC
  • Разделение сокетов между хостом и докер-контейнером

Самый простой способ — заключается в том, чтобы разделить мой сокет X11 с контейнером и использовать его напрямую. Идея довольно проста, и вы можете легко попробовать попробовать запустить контейнер Firefox, используя следующий файл Docker:

FROM ubuntu:14.04

RUN apt-get update && apt-get install -y firefox libcanberra-gtk-module packagekit-gtk3-module

# Replace 1000 with your user / group id
RUN export uid=1000 gid=1000 && \
mkdir -p /home/developer && \
echo "developer:x:${uid}:${gid}:Developer,,,:/home/developer:/bin/bash" >> /etc/passwd && \
echo "developer:x:${uid}:" >> /etc/group && \
echo "developer ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/developer && \
chmod 0440 /etc/sudoers.d/developer && \
chown ${uid}:${gid} -R /home/developer

USER developer
ENV HOME /home/developer
CMD /usr/bin/firefox

Запускаем:

$ docker build -t firefox .

Запускаем контейнер:

docker run -ti --rm \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
firefox

И вот что получилось:

Запуск FireFox в Docker

Данный пример приводился на CentOS 6, но у меня есть еще MacOS X и я бы хотел все же подружить все это дело!

Ставим пакет для MacOS X:

$ brew install caskroom/cask/xquartz

Запускаем:

$ open -a XQuartz

Открываем натройки, переходим во вкладку «security» и выставляем все галочки (2 шт):

Настройка Security в XQuartz

Идем далее:

$ ip=$(ifconfig en0 | grep inet | awk '$1=="inet" {print $2}')
$ xhost + $ip

И, запускаем:

$ docker run -it -e DISPLAY=$ip:0 -v /tmp/.X11-unix:/tmp/.X11-uni firefox

Хочу добавить следующее, я много перерыл в интернете, но мало что полезного нашел. И да, вот как у меня выглядят конфигы ssh.

# cat /etc/ssh/ssh_config | grep -Ev "(#|^$)"

 Host *
   ForwardAgent yes
   ForwardX11 yes
Host *
	SendEnv LANG LC_*
Host *
    XAuthLocation /opt/X11/bin/xauth

И:

# cat /etc/ssh/sshd_config | grep -Ev "(#|^$)"

AuthorizedKeysFile	.ssh/authorized_keys
UsePAM yes
X11Forwarding yes
X11DisplayOffset 10
AcceptEnv LANG LC_*
Subsystem	sftp	/usr/libexec/sftp-server
XAuthLocation /opt/X11/bin/xauth

Приведу еще пример.

-=== Запуск spotify в Docker ===-

Выполняем:

$ ip=$(ifconfig eth1 | grep inet | awk '$1=="inet" {print $2}'| cut -c6-)
$ xhost + $ip

PS: У меня тут используется несколько интерфейсов, но я взял ИП с eth1.

Или, для MacOS X:

$ ip=$(ifconfig en0 | grep inet | awk '$1=="inet" {print $2}')
$ xhost + $ip

Не буду собирать контейнер с Dockerfile, возьму готовый имедж:

$ # docker run --privileged --name spotify -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix jess/spotify

Я тут пробросил звуковое устройство во внутрь самого контейнера.

Запуск spotify в Docker

Вот это шайтаны додумались)))

На маке я пока не понял как это сделать 🙁

-=== Запуск wireshark в Docker ===-

Выполняем:

$ ip=$(ifconfig eth1 | grep inet | awk '$1=="inet" {print $2}'| cut -c6-)
$ xhost + $ip

PS: У меня тут используется несколько интерфейсов, но я взял ИП с eth1.

Или, для MacOS X:

$ ip=$(ifconfig en0 | grep inet | awk '$1=="inet" {print $2}')
$ xhost + $ip

Или посмотреть все ИП на всех интерфейсах:

$ ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'

 

Не буду собирать контейнер с Dockerfile, возьму готовый имедж:

$ docker run -ti --net=host --privileged --name wireshark1 -v $HOME:/root:rw -e XAUTHORITY=/root/.Xauthority -e DISPLAY=$ip:0 manell/wireshark

Получаем:

Запуск wireshark в Docker

Работает даже!

Полезная статья:

Утилита xhost в Unix/Linux

Вот и все, статья «Запуск GUI-приложения в Docker» завершена.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.