Автор выбрал фонд Free and Open Source Fund для получения пожертвования в рамках программы Write for DOnations.
Несмотря на рост популярности облачных сервисов, необходимость использования локальных приложений все еще существует.
Используя noVNC и TigerVNC, вы можете запускать локальные приложения в контейнере Docker и удаленно подключаться к ним через браузер. Кроме того, вы можете запускать свои прложения на сервере, где доступно больше системных ресурсов, чем в локальной системе, что обеспечивает дополнительную гибкость при запуске ресурсоемких приложений.
В этом обучающем модуле мы используем Docker для контейнеризации клиента электронной почты Mozilla Thunderbird. После этого мы защитим контейнер и предоставим удаленной доступ к нему с помощью веб-сервера Caddy.
После завершения работы вы сможете подключаться к Thunderbird с любого устройства, используя только браузер. Также у вас будет возможность локального доступа к его файлам через WebDAV. У вас также будет автономный образ Docker, который вы сможете использовать где угодно.
Для прохождения этого обучающего модуля вам потребуется следующее:
sudo
.Мы запустили сервер и установили Docker и теперь можем начать настройку контейнера нашего приложения. Поскольку контейнер состоит из нескольких компонентов, необходимо использовать диспетчер процессов для их запуска и мониторинга. Мы будем использовать supervisord
. Диспетчер процессов supervisord
написан на языке Python и часто используется для организации сложных контейнеров.
Вначале создайте для контейнера каталог thunderbird
и войдите в него:
- mkdir ~/thunderbird
- cd ~/thunderbird
Затем создайте файл supervisord.conf
и откройте его в nano
или другом предпочитаемом редакторе:
- nano supervisord.conf
Добавьте в файл supervisord.conf
этот первый блок кода, определяющий глобальные опции supervisord:
[supervisord]
nodaemon=true
pidfile=/tmp/supervisord.pid
logfile=/dev/fd/1
logfile_maxbytes=0
В этом блоке выполняется непосредственно настройка supervisord
. Для параметра nodaemon
следует задать значение true
, поскольку он будет работать внутри контейнера Docker в качестве входной точки. Поэтому нам нужно, чтобы процесс работал в активном режиме. Для pidfile
следует задать путь, доступный пользователю без привилегий root (подробности ниже), а для параметра logfile
— значение stdout, чтобы вы могли просматривать журналы.
Добавьте еще один небольшой блок кода в файл supervisord.conf
. Этот блок запускает TigerVNC, комбинированный сервер VNC/X11:
...
[program:x11]
priority=0
command=/usr/bin/Xtigervnc -desktop "Thunderbird" -localhost -rfbport 5900 -SecurityTypes None -AlwaysShared -AcceptKeyEvents -AcceptPointerEvents -AcceptSetDesktopSize -SendCutText -AcceptCutText :0
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
В этом блоке мы настраиваем сервер X11. X11 — это протокол сервера дисплея, позволяющийо запускать приложения с графическим интерфейсом. В будущем он будет заменен протоколом Wayland, однако на данный момент удаленный доступ все еще находится в стадии разработки.
Для этого контейнера мы используем TigerVNC и его встроенный сервер VNC. Это дает ряд преимуществ по сравнению с использованием отдельных серверов X11 и VNC:
При желании вы можете изменить аргумент опции -desktop
для Thunderbird
на другое желаемое значение. Сервер будет выводить выбранный вариант в качестве заголовка веб-страницы доступа к вашему приложению.
Добавим третий блок кода в supervisord.conf
для запуска easy-novnc
:
...
[program:easy-novnc]
priority=0
command=/usr/local/bin/easy-novnc --addr :8080 --host localhost --port 5900 --no-url-password --novnc-params "resize=remote"
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
В этом блоке мы настраиваем независимый сервер easy-novnc
, обеспечивающий оболочку для noVNC. Этот сервер выполняет две роли. Во первых, он предоставляет простую страницу подключения, где вы можете настроить параметры подключения и задать параметры по умолчанию. Во вторых, он выступает в качестве прокси для VNC через WebSocket, обеспечивая возможность доступа через обычный браузер.
Изменение размера обычно выполняется на стороне клиента (т. е. масштабирование изображения), но мы используем опцию resize=remote
, чтобы в полной мере воспользоваться преимуществами дистанционной коррекции разрешения TigerVNC. Также при этом обеспечивается более низкое время задержки на медленных устройствах, таких как Chromebook нижнего ценового диапазона:
Примечание. В этом обучающем модуле используется easy-novnc
. При желании вы можете использовать websockify
и отдельный веб-сервер. Преимущество easy-novnc
заключается в значительно меньшем использовании памяти и более быстром запуске, а также в его автономности. Кроме того, easy-novnc
использует более простую страницу подключения по сравнению с вариантом по умолчанию noVNC и позволяет настраивать полезные параметры по умолчанию (например, resize=remote
).
Добавьте в конфигурацию следующий блок, чтобы запустить диспетчер окон OpenBox:
...
[program:openbox]
priority=1
command=/usr/bin/openbox
environment=DISPLAY=:0
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
В этом блоке мы настраиваем OpenBox, компактный диспетчер окон X11. Вы можете пропустить этот шаг, но без него у вас не будет панелей заголовков или возможности изменять размер окон.
В заключение добавим в файл supervisord.conf
последний блок, который запустит основное приложение:
...
[program:app]
priority=1
environment=DISPLAY=:0
command=/usr/bin/thunderbird
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
В этом последнем блоке мы устанавливаем для параметра priority
значение 1
, чтобы Thunderbird запускался после TigerVNC, поскольку в ином случае он будет запускаться через очередь, то есть иногда вообще не запускаться. Также мы зададим autorestart=true
, чтобы приложение автоматически открывалось заново, если оно случайно закроется. Переменная среды DISPLAY
указывает, что приложение должно выводиться на ранее настроенном сервере VNC.
Вот так будет выглядеть заполненный файл supervisord.conf
:
[supervisord]
nodaemon=true
pidfile=/tmp/supervisord.pid
logfile=/dev/fd/1
logfile_maxbytes=0
[program:x11]
priority=0
command=/usr/bin/Xtigervnc -desktop "Thunderbird" -localhost -rfbport 5900 -SecurityTypes None -AlwaysShared -AcceptKeyEvents -AcceptPointerEvents -AcceptSetDesktopSize -SendCutText -AcceptCutText :0
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:easy-novnc]
priority=0
command=/usr/local/bin/easy-novnc --addr :8080 --host localhost --port 5900 --no-url-password --novnc-params "resize=remote"
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:openbox]
priority=1
command=/usr/bin/openbox
environment=DISPLAY=:0
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:app]
priority=1
environment=DISPLAY=:0
command=/usr/bin/thunderbird
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
Если вы захотите поместить в контейнер другое приложение, замените /usr/bin/thunderbird
путем к исполняемому файлу вашего приложения. Если нет, то теперь вы можете настроить главное меню графического пользовательского интерфейса.
Мы настроили диспетчер процессов, и теперь можем перейти к настройке меню OpenBox. Это меню позволяет нам запускать приложения внутри контейнера. При необходимости мы также добавим терминал и монитор процессов для целей отладки.
Откройте каталог вашего приложения и используйте nano
или другой предпочитаемый редактор, чтобы создать и открыть новый файл с именем menu.xml
:
- nano ~/thunderbird/menu.xml
Добавьте в файл menu.xml
следующий код:
<?xml version="1.0" encoding="utf-8"?>
<openbox_menu xmlns="http://openbox.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://openbox.org/ file:///usr/share/openbox/menu.xsd">
<menu id="root-menu" label="Openbox 3">
<item label="Thunderbird">
<action name="Execute">
<execute>/usr/bin/thunderbird</execute>
</action>
</item>
<item label="Terminal">
<action name="Execute">
<execute>/usr/bin/x-terminal-emulator</execute>
</action>
</item>
<item label="Htop">
<action name="Execute">
<execute>/usr/bin/x-terminal-emulator -e htop</execute>
</action>
</item>
</menu>
</openbox_menu>
Этот файл XML содержит элементы меню, которые появятся, если нажать правой кнопкой мыши на рабочем столе. Каждый элемент состоит из ярлыка и действия.
Если вы хотите поместить в контейнер другое приложение, замените /usr/bin/thunderbird
путем к исполняемому файлу вашего приложения и измените ярлык
элемента.
Мы настроили OpenBox и теперь перейдем к созданию файла Dockerfile, который объединяет все вместе.
Создайте файл Dockerfile в каталоге вашего контейнера:
- nano ~/thunderbird/Dockerfile
Вначале добавим код для создания easy-novnc
:
FROM golang:1.14-buster AS easy-novnc-build
WORKDIR /src
RUN go mod init build && \
go get github.com/geek1011/easy-novnc@v1.1.0 && \
go build -o /bin/easy-novnc github.com/geek1011/easy-novnc
На первом этапе мы создаем easy-novnc
. Мы выделили это в отдельный шаг для простоты и экономии места — в окончательном образе нам не нужна вся цепочка инструментов Go. Обратите внимание на @v1.1.0
в команде build. Это обеспечивает детерминистический результат, что важно, поскольку Docker кэширует результаты каждого шага. Если мы прямо не укажем версию, Docker будет ссылаться на последнюю версию easy-novnc
на момент создания образа. Кроме того, нам нужно гарантировать загрузку определенной версии easy-novnc
на случай радикальных изменений в интерфейсе командной строки.
На втором этапе мы создадим окончательный образ. Здесь мы будем использовать Debian 10 (buster) как базовый образ. Обратите внимание, что поскольку приложение работает в контейнере, оно будет работать вне зависимости от версии дистрибутива на вашем сервере.
Затем добавьте следующий блок в файл Dockerfile
:
...
FROM debian:buster
RUN apt-get update -y && \
apt-get install -y --no-install-recommends openbox tigervnc-standalone-server supervisor gosu && \
rm -rf /var/lib/apt/lists && \
mkdir -p /usr/share/desktop-directories
Согласно этой инструкции мы устанавливаем Debian 10 как базовый образ, а затем устанавливаем минимальный набор средств для запуска в контейнере приложений с графическим интерфейсом. Обратите внимание, что мы запускаем run apt-get update
в рамках той же инструкции, чтобы предотвратить проблемы с кэшированием в Docker. Для экономии места мы также удаляем списки пакетов, загруженные после этого (сами кэшированные пакеты удаляются по умолчанию). Также мы создаем каталог /usr/share/desktop-directories
, поскольку некоторые приложения зависят от наличия этого каталога.
Добавим еще один небольшой блок кода:
...
RUN apt-get update -y && \
apt-get install -y --no-install-recommends lxterminal nano wget openssh-client rsync ca-certificates xdg-utils htop tar xzip gzip bzip2 zip unzip && \
rm -rf /var/lib/apt/lists
В этой инструкции мы устанавливаем некоторые полезные утилиты и пакеты общего назначения. Особенный интерес представляют утилиты xdg-utils
(которые обеспечивают базовые команды, используемые приложениями рабочего стола в Linux) и ca-certificates
(устанавливают корневые сертификаты для доступа к сайтам HTTPS).
Теперь мы можем добавить инструкции для основного приложения:
...
RUN apt-get update -y && \
apt-get install -y --no-install-recommends thunderbird && \
rm -rf /var/lib/apt/lists
Как и ранее, здесь мы устанавливаем приложение. Если вы помещаете в контейнер другое приложение, вы можете заменить эти команды теми, которые требуются для установки конкретного приложения. Для запуска некоторых приложений в Docker могут потребоваться дополнительные действия. Например, при установке приложения, которое использует Chrome, Chromium или QtWebEngine, нам нужно будет использовать аргумент командной строки --no-sandbox
, потому что в Docker это не поддерживается.
Затем добавим инструкции для добавления в контейнер последних нескольких файлов:
...
COPY /bin/easy-novnc /usr/local/bin/
COPY menu.xml /etc/xdg/openbox/
COPY supervisord.conf /etc/
EXPOSE 8080
Здесь мы добавляем в образ ранее созданные файлы конфигурации и копируем двоичный файл easy-novnc
с первого этапа.
Следующий блок кода создает каталог для данных и добавляет выделенного пользователя для вашего приложения. Это важно, поскольку некоторые приложения не запускаются с привилегиями root. Кроме того, приложения не рекомендуется запускать с привилегиями root даже в контейнере.
...
RUN groupadd --gid 1000 app && \
useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && \
mkdir -p /data
VOLUME /data
Чтобы обеспечить согласованность идентификаторов UID/GID
для файлов, нужно явно задать для обоих параметров значение 1000
. Также мы монтируем том в каталог данных, чтобы он сохранялся при перезапуске системы.
В заключение добавим инструкции для запуска всех элементов:
...
CMD ["sh", "-c", "chown app:app /data /dev/stdout && exec gosu app supervisord"]
Если задать команду по умолчанию supervisord
, диспетчер будет запускать необходимые процессы для работы вашего приложения. В этом случае мы используем CMD
вместо ENTRYPOINT
. В большинстве случаев разницы не будет, однако есть несколько причин, по которым CMD
лучше подходит для этой цели. Во первых, supervisord
не принимает никакие нужные нам аргументы, а если передавать аргументы в контейнер, они заменяют CMD
и добавляются к ENTRYPOINT
. Во вторых, использование CMD
позволяет нам отправлять при передаче аргументов в контейнер совершенно другую команду (которая выполняется /bin/sh -c
), что упрощает отладку.
Кроме того, нам нужно запускать chown
с првилегиями root перед запуском supervisord
, чтобы предотвратить проблемы с разрешениями доступа к тому данным и дать дочерним процессам возможность открывать stdout
. Также это означает, что для переключения пользователя нам нужно использовать gosu
вместо инструкции USER
.
Вот так будет выглядеть заполненный файл Dockerfile
:
FROM golang:1.14-buster AS easy-novnc-build
WORKDIR /src
RUN go mod init build && \
go get github.com/geek1011/easy-novnc@v1.1.0 && \
go build -o /bin/easy-novnc github.com/geek1011/easy-novnc
FROM debian:buster
RUN apt-get update -y && \
apt-get install -y --no-install-recommends openbox tigervnc-standalone-server supervisor gosu && \
rm -rf /var/lib/apt/lists && \
mkdir -p /usr/share/desktop-directories
RUN apt-get update -y && \
apt-get install -y --no-install-recommends lxterminal nano wget openssh-client rsync ca-certificates xdg-utils htop tar xzip gzip bzip2 zip unzip && \
rm -rf /var/lib/apt/lists
RUN apt-get update -y && \
apt-get install -y --no-install-recommends thunderbird && \
rm -rf /var/lib/apt/lists
COPY /bin/easy-novnc /usr/local/bin/
COPY menu.xml /etc/xdg/openbox/
COPY supervisord.conf /etc/
EXPOSE 8080
RUN groupadd --gid 1000 app && \
useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && \
mkdir -p /data
VOLUME /data
CMD ["sh", "-c", "chown app:app /data /dev/stdout && exec gosu app supervisord"]
Сохраните и закройте файл Dockerfile
. Теперь мы готовы выполнить сборку и запуск контейнера и использовать Thunderbird — приложение с графическим интерфейсом.
На следующем шаге мы выполним сборку контейнера и настроим его для запуска при загрузке системы. Также мы настраиваем том для сохранения данных приложений при перезапуске или обновлении.
Вначале выполним сборку контейнера. Запустите эти команды в каталоге ~/thunderbird
:
- docker build -t thunderbird .
Создайте новую сеть, которая будет общей для контейнеров приложения:
- docker network create thunderbird-net
Создайте том для хранения данных приложения:
- docker volume create thunderbird-data
Запустите приложение и установите автоматический перезапуск:
- docker run --detach --restart=always --volume=thunderbird-data:/data --net=thunderbird-net --name=thunderbird-app thunderbird
Если хотите, вы можете заменить thunderbird-app
после опции --name
названием другого приложения. Какой бы вариант вы ни выбрали, теперь ваше приложение помещено в контейнер и запущено. Теперь мы используем веб-сервер Caddy, чтобы защитить его и удаленно подключаться к нему.
На этом шаге мы настроим веб-сервер Caddy для аутентификации и (при желании) удаленного доступа к файлам через WebDAV. Для удобства и возможности использования с обратным прокси-сервером мы запустим его в другом контейнере.
Создайте новый каталог и перейдите в него:
- mkdir ~/caddy
- cd ~/caddy
Создайте новый файл Dockerfile
в nano
или другом предпочитаемом редакторе:
- nano ~/caddy/Dockerfile
Добавьте в него следующие директивы:
FROM golang:1.14-buster AS caddy-build
WORKDIR /src
RUN echo 'module caddy' > go.mod && \
echo 'require github.com/caddyserver/caddy/v2 v2.0.0' >> go.mod && \
echo 'require github.com/mholt/caddy-webdav v0.0.0-20200523051447-bc5d19941ac3' >> go.mod
RUN echo 'package main' > caddy.go && \
echo 'import caddycmd "github.com/caddyserver/caddy/v2/cmd"' >> caddy.go && \
echo 'import _ "github.com/caddyserver/caddy/v2/modules/standard"' >> caddy.go && \
echo 'import _ "github.com/mholt/caddy-webdav"' >> caddy.go && \
echo 'func main() { caddycmd.Main() }' >> caddy.go
RUN go build -o /bin/caddy .
FROM debian:buster
RUN apt-get update -y && \
apt-get install -y --no-install-recommends gosu && \
rm -rf /var/lib/apt/lists
COPY /bin/caddy /usr/local/bin/
COPY Caddyfile /etc/
EXPOSE 8080
RUN groupadd --gid 1000 app && \
useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && \
mkdir -p /data
VOLUME /data
WORKDIR /data
CMD ["sh", "-c", "chown app:app /data && exec gosu app /usr/local/bin/caddy run -adapter caddyfile -config /etc/Caddyfile"]
Этот файл Dockerfile выполняет сборку Caddy с включенным плагином WebDAV и запускает его на порту 8080
с файлом Caddyfile
в каталоге /etc/Caddyfile
. Сохраните и закройте файл.
Далее мы настроим веб-сервер Caddy. Создайте файл с именем Caddyfile
в только что созданном вами каталоге:
- nano ~/caddy/Caddyfile
Добавьте следующий блок кода в файл Caddyfile
:
{
order webdav last
}
:8080 {
log
root * /data
reverse_proxy thunderbird-app:8080
handle /files/* {
uri strip_prefix /files
file_server browse
}
redir /files /files/
handle /webdav/* {
uri strip_prefix /webdav
webdav
}
redir /webdav /webdav/
basicauth /* {
{env.APP_USERNAME} {env.APP_PASSWORD_HASH}
}
}
Этот файл Caddyfile
выступает в качестве посредника между корневым каталогом и контейнером thunderbird-app
, который мы создали на шаге Step 4 (Docker разрешает его с правильным IP-адресом). Также он выступает в качестве файлового веб-браузера с доступом только для чтения к каталогу /files
и запускает сервер WebDAV в каталоге /webdav
, который вы можете монтировать в локальную систему для доступа к своим файлам. Имя пользователя и пароль считываются из переменных среды APP_USERNAME
и APP_PASSWORD_HASH
.
Выполните сборку контейнера:
- docker build -t thunderbird-caddy .
Caddy v.2 требует хэшировать желаемый пароль. Запустите следующую команду, заменив mypass
надежным паролем по своему выбору:
- docker run --rm -it thunderbird-caddy caddy hash-password -plaintext 'mypass'
Эта команда выведет строку символов. Скопируйте их в буфер обмена для подготовки к запуску следующей команды.
Теперь вы готовы запустить контейнер. Замените myuser
желаемым именем пользователя и замените mypass-hash
выводом команды, выполненной на предыдущем шаге. Также вы можете сменить порт (здесь 8080
) для доступа к серверу:
- docker run --detach --restart=always --volume=thunderbird-data:/data --net=thunderbird-net --name=thunderbird-web --env=APP_USERNAME="myuser" --env=APP_PASSWORD_HASH="mypass-hash" --publish=8080:8080 thunderbird-caddy
Теперь мы готовы подключиться к приложению и протестировать его.
Откроем приложение и проверим его работу.
Откройте в браузере адрес http://your_server_ip:8080
, введите ранее выбранные учетные данные и нажмите Connect.
Теперь вы должны иметь возможность взаимодействия с приложением, а его размер экрана должен автоматически измениться для соответствия размеру окна браузера.
Если нажать правой кнопкой мыши на черный рабочий стол, вы увидите меню, позволяющее открыть терминал. Если вы нажмете среднюю кнопку мыши, вы увидите список окон.
Откройте в браузере адрес http://your_server_ip:8080/files/
. Теперь у вас должен быть доступ к вашим файлам.
Также вы можете смонтировать http://your_server_ip:8080/webdav/
в клиент WebDAV. Теперь у вас должен иметься прямой доступ к файлам с возможностью их изменения. Если вы используете опцию «Подключение сетевого диска» в Проводнике Windows, вам нужно будет использовать обратный прокси для добавления HTTPS или задать для параметра HKLM\SYSTEM\CurrentControlSet\Services\WebClient\Parameters\BasicAuthLevel
значение DWORD:2
.
В любом случае ваше приложение с графическим интерфейсом теперь доступно для удаленного использования.
Мы успешно настроили контейнер Docker для приложения Thunderbird и использовали Caddy для настройки доступа к нему через браузер. Если вам потребуется обновить приложение, остановите контейнеры, запустите команду docker rm thunderbird-app thunderbird-web
, выполните сборку образов заново и снова запустите команды docker run
, как было указано в предыдущих шагах. Ваши данные хранятся на томе, и поэтому они останутся доступными.
Если вы хотите узнать больше о базовых командах Docker, ознакомьтесь с этим обучающим модулем или этим справочником. Если вы планируете долгосрочное использование, вы можете включить протокол HTTPS (для этого требуется домен) для дополнительной безопасности.
Если вы развертываете несколько приложений, вы можете использовать Docker Compose или Kubernetes вместо того, чтобы запускать каждый контейнер вручную. Этот обучающий модуль можно использовать для запуска любых других приложений Linux на вашем сервере, в том числе:
Последний вариант открывает большой потенциал контейнеризации приложений с графическим интерфейсом и удаленного доступа к ним. При такой настройке вы можете использовать для запуска ресурсоемких инструментов, таких как Cutter, не локальную систему, а сервер, имеющий большую вычислительную мощность.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!