Запуск графических приложений в Docker

Введение.

В основном, Docker привыкли использовать для запуска сервисов или процессов не предполагающих визуальной составляющей. Однако могут быть ситуации, когда в контейнере возникает необходимость открыть среду разработки и на месте продебажить один сервис или два сервиса, каждый в своем контейнере. В прошлой статье я писал, как запускать графические приложения в wsl, в текущей расскажу, как можно запустить разными способами в контейнере qtcreator. Аналогично можно будет поступить с любой средой разработки или программой.

Подготовка.

Включаем wsl на windows, если он отключен, говорим ему работать в режиме wsl2 — ровно так как описано в документации Майкрософт.  Устанавливаем Docker.
Создаем для примера контейнер simpleimg_0 на основе ubuntu:18.04 и тут же запускаем его в интерактивном режиме:

docker run --name simpleimg_0 -it ubuntu:18.04 /bin/bash

8da953b9732884e55d82792ad94397f9.png

Поставим mc по желанию для своего удобства:

apt update && apt install mc

8b79f250056e6d53160158b8222c9335.png

Согласимся доставить все зависимости и получим:

24b4b7fbb8bfc0959b1697ebf5d9762d.png

Установка qtcreator.

Теперь устанавливаем qtcreator:

apt install qtcreator

Соглашаемся добавить 1,6 гб зависимостей:

15f76d21558f1db40e48f77359f1c3da.png

При попытке запуска видим, что ничего не получиться. Теперь решим этот вопрос.

6396084f1a59aea057e3d6b5f1ddcaef.png

Запуск qtcreator.

Для того чтобы запустить графическое приложение из консоли линукс нужно на хостовой системе запустить х-сервер, в нашем случае это Vcxsrv.
Указываем при его запуске номер дисплея и запоминаем его:

81ea3b5cac172c4e17fe88a6880f4370.png

На следующей оставляем все по умолчанию:

51a5717fb7aeb5012e0dc35a33884e8c.png

На последней снимаем средний флажок и устанавливаем последний:

b295536cdf213587be4f979102e031f6.png

Теперь видим значек х-сервера в трее около часов.

Теперь нам нужно узнать заветный адрес х-сервера, который нужно нам указать в контейнере. Для этого открываем консоль на хостовой ОС и запрашиваем информацию по сети:

ipconfig

Находим в выводе информацию по сети wsl:

6b805c0fdcbc95b3c97f596678ae92da.png

Берем этот адрес и вносим его в переменную окружения DISPLAY в ОС контейнера, вспомнив про номер дисплея:

export DISPLAY=172.30.144.1:0

Повторно запускаем qtcreator и ура, он запускается в окне на заднем плане за консолью:

a462acec2529a75d521d2a36b95e113f.png

Заходим в настройки, проверяем, что компиляторы уже стоят:

5a437efbc48f96bb3a0eb5da43557b96.png

Есть дебаггер, но нет Cmake и нет комплекта Qt.

5a949ea00feddae957fbb31725e51212.png

Закрываем окно программы, переходим в консоль и доставляем Cmake:

apt install cmake

Проверяем и видим, что при следующем запуске Cmake уже подтянулся в настройках:

ef87e872597bf0c0131e7a1c6e82697f.png

Любые зависимости таким образом можно самостоятельно доставить.

Посмотрим сколько весит наш контейнер для отладки — не очень то и много, т.к в несколько раз меньше чем виртуальная машина на основе убунты:

b54a31bcb929cf4501965810f9c25414.png

После того как мы завершили работу с контейнером, то запустить его снова в интерактивном режиме можно командой:

docker container start simpleimg_0 –i

Dockerfile, автоматизация.

Все то, что мы выше проделали руками, можно прописать в Dockerfile и создать все одной командой из консоли.

Qt в dockerfile можно установить несколькими способами — через apt install, через python утилиту aqt, через офиц. установщики qt.

Первый способ в ручном режиме разобрали в первой части статьи, предлагаю ниже разобрать установку через python. Создаем Dockerfile со следующим содержимым и переходим в консоли к нему на один уровень:

FROM ubuntu:18.04

RUN apt-get -y update && DEBIAN_FRONTEND=noninteractive apt-get -y install \
    git \
    cmake \
    python3 \
    python3-pip \
    build-essential \
    libdbus-1-3 \
    libpulse-mainloop-glib0

RUN pip3 install aqtinstall

ARG QT=5.11.0
ARG QT_TOOLS=tools_qtcreator
ARG QT_HOST=linux
ARG QT_TARGET=desktop
ARG QT_ARCH=gcc_64
RUN aqt install-qt --outputdir /opt/qt ${QT_HOST} ${QT_TARGET} ${QT} ${QT_ARCH}
RUN aqt install-tool -O /opt/qtcreator ${QT_HOST} ${QT_TARGET} ${QT_TOOLS}

ENV PATH /opt/qt/${QT}/gcc_64/bin:/opt/qtcreator/Tools/QtCreator/bin:$PATH
ENV QT_PLUGIN_PATH /opt/qt/${QT}/gcc_64/plugins/
ENV QML_IMPORT_PATH /opt/qt/${QT}/gcc_64/qml/
ENV QML2_IMPORT_PATH /opt/qt/${QT}/gcc_64/qml/

Здесь мы сначала ставить необходимые зависимости для сборки, после этого ставим утилиту aqtinstall и выполняем ей две команды:
aqt install-qt — устанавливает библиотеки qt
aqt install-tool — устанавливает qtcreator.

Теперь создаем образ из этого файла:

docker build -f Dockerfile -t simpleimg_qtcr .

Запускаем, и вспоминаем, что нужно прописать актуальный адрес DISPLAY.

Минус этого способа — долгая установка, т.к все стягивается с офис.репозитория Qt. Документация для утилиты aqt.

Разберем еще один способ, лишенного этого недостатка — установка с использованием оффлайн-установщика с офиц. сайта. Качаем с офиц. репозитория установщик. Качаем скрипт не интерактивного установщика qt-installer-noninteractive.qs. Открываем последний скрипт в редакторе и кое-что правим.
Вписываем данные авторизации:

Controller.prototype.CredentialsPageCallback = function() {
  var page = gui.pageWidgetByObjectName("CredentialsPage");
  page.loginWidget.EmailLineEdit.setText("email");
  page.loginWidget.PasswordLineEdit.setText("pass");
  gui.clickButton(buttons.NextButton);
}

Вписываем куда установить фреймворк:

Controller.prototype.TargetDirectoryPageCallback = function()
{
    //dev is the user in our docker image
    gui.currentPageWidget().TargetDirectoryLineEdit.setText(installer.value("/opt/Qt5.11.0"));
    gui.clickButton(buttons.NextButton);
}

В разделе Controller.prototype.ComponentSelectionPageCallback = function () указываем какие компоненты установить, а какие нет:

widget.deselectAll();
    widget.selectComponent("qt.tools.qtcreator");
    widget.selectComponent("qt.qt5.5110");
    widget.selectComponent("qt.qt5.5110.gcc_64");
    
    widget.deselectComponent("qt.qt5.5110.android_x86");
    widget.deselectComponent("qt.qt5.5110.android_armv7");
    widget.deselectComponent("qt.qt5.5110.android_armv7");
    widget.deselectComponent("qt.qt5.5110.doc");
    widget.deselectComponent("qt.qt5.5110.examples");
    widget.deselectComponent("qt.qt5.5110.src");

На одном уровне с установщиком создаем dockerfile:

# run as:
# docker build -t offline_qtcr -f Dockerfile path_to_distrib
FROM ubuntu:18.04
MAINTAINER Chesnochenko 

ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && \
    apt -o Dpkg::Options::="--force-confold" install -q -y mc build-essential \
    mesa-common-dev libglu1-mesa-dev libdbus-1-dev x11-xkb-utils sudo curl \
    libfontconfig1 libxrender1 libxi6 libgconf-2-4 libxcb-xinerama0 gdb cmake && \
    apt clean && rm -rf /var/lib/apt/lists/*

RUN useradd -G users,video -ms /bin/bash user && \
	echo 'user:12345678' | chpasswd -e && \
	echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
	mkdir /run/user && \
    chown user:user /run/user && \	
	apt update && apt install -y locales language-pack-ru libfontconfig1 && \
    update-locale LANG=ru_RU.UTF-8 && \
	echo 'LANG=ru_RU.UTF-8' >> /etc/default/locale && \
	echo 'export LC_ALL=ru_RU.UTF-8' >> /home/user/.bashrc && \
	echo 'export LANG=ru_RU.UTF-8' >> /home/user/.bashrc && \
	locale-gen ru_RU.UTF-8 && \
	apt install -y --reinstall locales && \	
	apt clean && rm -rf /var/lib/apt/lists/* 

WORKDIR /tmp/
COPY qt-opensource-linux-x64-5.11.0.run .
COPY qt-installer-noninteractive.qs .
RUN chmod a+x qt-opensource-linux-x64-5.11.0.run \
    && chmod a+x qt-installer-noninteractive.qs 

RUN curl https://www.openssl.org/source/openssl-1.0.2l.tar.gz | tar xz && cd openssl-1.0.2l && \
    ./config && make -j $(nproc) && make install && \
    ln -sf /usr/local/ssl/bin/openssl `which openssl`

RUN ./qt-opensource-linux-x64-5.11.0.run -platform minimal --verbose --script ./qt-installer-noninteractive.qs
RUN rm -f qt-opensource-linux-x64-5.11.0.run qt-installer-noninteractive.qs

ENV DISPLAY=172.30.144.1:0
RUN echo 'export DISPLAY=172.30.144.1:0' >> /home/user/.bashrc
#RUN setxkbmap -model pc105 -layout us,ru -option grp:alt_shift_toggle


ARG QT=5.11.0
ARG QT_HOST=linux
ARG QT_TARGET=desktop
ARG QT_ARCH=gcc_64
ENV PATH /opt/Qt${QT}/${QT}/gcc_64/bin:/opt/Qt${QT}/Tools/QtCreator/bin/:$PATH
ENV QT_PLUGIN_PATH /opt/Qt${QT}/${QT}/gcc_64/plugins/
ENV QML_IMPORT_PATH /opt/Qt${QT}/${QT}/gcc_64/qml/
ENV QML2_IMPORT_PATH /opt/Qt${QT}/${QT}/gcc_64/qml/

ENV SHELL=/bin/bash
CMD bash
	
USER user
WORKDIR /home/user
CMD bash

В эту сборку добавили отдельного пользователя user, поддержку ввода на русском языке и возможность переключаться по alt+cntrl.

Два файла: qt-opensource-linux-x64–5.11.1.run и qt-installer-noninteractive.qs скопировали в контейнер, в папку /tmp. После установки подчистили за собой место. Сразу сохранили в контейнер информацию об адресе х-сервера. В случае, когда после перезагрузки компьютера адрес изменится, мы его установим через параметры запуска контейнера.

Создаем образ, в конце добавляем путь к каталогу где лежат дистрибутив qt и скрипт .qs:

docker build -t offline_qtcr -f Dockerfile d:\Distrib\Qt\single\qreator_linux\

Запускаем:

docker run --name offqtcont -it offline_qtcr /opt/Qt5.11.0/Tools/QtCreator/bin/qtcreator.sh

Запустился qtcreator:

ee01af65b668bb3721400f8e364805b3.png

Можем при запуске не указывать команду запуска самого qtcreator, тогда откроется консоль и в ней уже можем ввести путь до исполняемого файла.

Заключение.

Вы познакомились с тремя способами установить и запустить qtcreator в докере. Если вам не нужен сам qtcreator, то по такому же принципу можно в докере развернуть любое графическое приложение.

© Habrahabr.ru