Запуск GUI-приложения в Docker
Хочу поведать историю о том, как я запустил GUI-приложение в Docker. некоторые спросят, зачем? Ну есть много причин, вот некоторые из них:
- Нет нужного пакета/утилиты под твою ОС.
- Проверить что получится.
Запуск GUI-приложения в Docker
Начну с теории и закончу примерами.
Архитектура X window system 101
В *NIX системах, приложение GUI имеет роль «X-клиента». Каждый раз, когда он перерисовывает свое содержимое, последовательность графических команд кодируется все в X протокол используя библиотеку (обычно Xlib) и передается в сокет X11. На другом конце, X-сервер считывает такие команды из сокета и отображает их на дисплей:
Контейнерезированные (Containerizing) GUI приложения
Взглянув на архитектуру X window системы, ясно то, что для того, чтобы наши контейнеризованные графические приложения могли рисовать на экране, нам нужно предоставить ему доступ к сокету X11 (запись), и нам нужен X-сервер для использования и рендеринга графики команд на экран.
Мы можем подойти к этой проблеме с двух сторон:
- Мы можем связать xvfb и VNC-сервер с нашим образом контейнера.
- Мы можем совместно использовать сокет X11 хоста с контейнером в качестве внешнего раздела.
Первый подход — это тот, который проще реализовать, он работает из коробки и клиенты 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
Получаю:
Шикарно! Все заработало!
-=== Запуск 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
И вот что получилось:
Данный пример приводился на CentOS 6, но у меня есть еще MacOS X и я бы хотел все же подружить все это дело!
Ставим пакет для MacOS X:
$ brew install caskroom/cask/xquartz
Запускаем:
$ open -a XQuartz
Открываем натройки, переходим во вкладку «security» и выставляем все галочки (2 шт):
Идем далее:
$ 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
Я тут пробросил звуковое устройство во внутрь самого контейнера.
Вот это шайтаны додумались)))
На маке я пока не понял как это сделать 🙁
-=== Запуск 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
Получаем:
Работает даже!
Полезная статья:
Вот и все, статья «Запуск GUI-приложения в Docker» завершена.