如何在Ubuntu20.04上使用Docker和Caddy远程访问GUI应用程序

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。

介绍

即使随着云服务的日益普及,运行本机应用程序的需求仍然存在。

通过使用 noVNCTigerVNC,您可以在 Docker 容器内运行本机应用程序,并使用 Web 浏览器远程访问它们。 此外,您可以在具有比本地可用资源更多的系统资源的服务器上运行您的应用程序,这可以在运行大型应用程序时提供更大的灵活性。

在本教程中,您将使用 Docker 将电子邮件客户端 Mozilla Thunderbird 容器化。 之后,您将使用 Caddy Web 服务器保护它并提供远程访问。

完成后,您只需使用网络浏览器即可从任何设备访问 Thunderbird。 或者,您还可以使用 WebDAV 从本地访问文件。 您还将拥有一个完全独立的 Docker 映像,可以在任何地方运行。

先决条件

在开始本指南之前,您需要以下内容:

  • 一台 Ubuntu 20.04 服务器,至少 2GB RAM 和 4GB 磁盘空间。
  • 具有 sudo 权限的非 root 用户。
  • Docker 在您的服务器上设置。 您可以关注如何在Ubuntu 20.04上安装和使用Docker。

第 1 步 — 创建 supervisord 配置

现在您的服务器正在运行并且 Docker 已安装,您可以开始配置应用程序的容器了。 由于您的容器由多个组件组成,因此您需要使用流程管理器来启动和监控它们。 在这里,您将使用 supervisordsupervisord 是一个用 Python 编写的流程管理器,通常用于编排复杂的容器。

首先,为您的容器创建并输入一个名为 thunderbird 的目录:

mkdir ~/thunderbird
cd ~/thunderbird

现在使用 nano 或您喜欢的编辑器创建并打开一个名为 supervisord.conf 的文件:

nano ~/thunderbird/supervisord.conf

现在将第一段代码添加到 supervisord.conf 中,它将定义 supervisord 的全局选项:

~/thunderbird/supervisord.conf

[supervisord]
nodaemon=true
pidfile=/tmp/supervisord.pid
logfile=/dev/fd/1
logfile_maxbytes=0

在此块中,您正在配置 supervisord 本身。 您需要将 nodaemon 设置为 true 因为它将在 Docker 容器内作为入口点运行。 因此,您希望它保持在前台运行。 您还将 pidfile 设置为非 root 用户可访问的路径(稍后将详细介绍),并将 logfile 设置为标准输出,以便您查看日志。

接下来,将另一个小代码块添加到 supervisord.conf。 此块启动 TigerVNC,它是一个组合的 VNC/X11 服务器:

~/thunderbird/supervisord.conf

...
[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 是一个显示服务器协议,它允许 GUI 应用程序运行。 请注意,将来它将被 Wayland 取代,但远程访问仍在开发中。

对于此容器,您使用的是 TigerVNC 及其内置的 VNC 服务器。 与使用单独的 X11 和 VNC 服务器相比,这具有许多优点:

  • 更快的响应时间,因为 GUI 绘图直接在 VNC 服务器上完成,而不是在中间帧缓冲区(存储屏幕内容的内存)上完成。
  • 自动调整屏幕大小,允许远程应用程序自动调整大小以适应客户端(在本例中为您的 Web 浏览器窗口)。

如果您愿意,可以将 -desktop 选项的参数从 Thunderbird 更改为您选择的其他参数。 服务器会将您的选择显示为用于访问您的应用程序的网页的标题。

现在,让我们在 supervisord.conf 中添加第三个代码块来启动 easy-novnc

~/thunderbird/supervisord.conf

...
[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 的包装器。 该服务器执行两个角色。 首先,它提供了一个简单的连接页面,允许您配置连接选项,并允许您设置默认选项。 其次,它通过 WebSocket 代理 VNC,允许通过普通的 Web 浏览器访问它。

通常,调整大小是在客户端完成的(即 图像缩放),但您正在使用 resize=remote 选项来充分利用 TigerVNC 的远程分辨率调整。 这也为速度较慢的设备(例如低端 Chromebook)提供了更低的延迟:

注:本教程使用easy-novnc。 如果您愿意,您可以使用 websockify 和单独的 Web 服务器。 easy-novnc 的优点是内存使用和启动时间明显更低,而且它是独立的。 easy-novnc 还提供比默认 noVNC 更清晰的连接页面,并允许设置有助于此设置的默认选项(例如 resize=remote)。


现在将以下块添加到您的配置中以启动窗口管理器 OpenBox:

~/thunderbird/supervisord.conf

...
[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,这将启动主应用程序:

~/thunderbird/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 的样子:

~/thunderbird/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 替换为应用程序可执行文件的路径。 否则,您现在可以配置 GUI 的主菜单了。

第 2 步 — 设置 OpenBox 菜单

现在您的流程管理器已配置完毕,让我们设置 OpenBox 菜单。 这个菜单允许我们在容器内启动应用程序。 如果需要,我们还将包括一个用于调试的终端和进程监视器。

在您的应用程序目录中,使用 nano 或您喜欢的文本编辑器创建并打开一个名为 menu.xml 的新文件:

nano ~/thunderbird/menu.xml

现在将以下代码添加到 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 替换为应用程序可执行文件的路径,并更改项目的 label

第 3 步 - 创建 Dockerfile

现在 OpenBox 已配置好,您将创建 Dockerfile,它将所有内容联系在一起。

在容器目录中创建一个 Dockerfile:

nano ~/thunderbird/Dockerfile

首先,让我们添加一些代码来构建 easy-novnc

〜/雷鸟/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

在第一阶段,您正在构建 easy-novnc。 为了简单和节省空间,这是在一个单独的阶段完成的——你不需要在最终图像中使用整个 Go 工具链。 请注意构建命令中的 @v1.1.0。 这确保了结果是确定性的,这很重要,因为 Docker 缓存了每一步的结果。 如果您没有指定明确的版本,Docker 将在首次构建映像时引用 easy-novnc 的最新版本。 此外,您要确保下载特定版本的 easy-novnc,以防对 CLI 界面进行重大更改。

现在让我们创建第二个阶段,它将成为最终图像。 在这里,您将使用 Debian 10 (buster) 作为基础镜像。 请注意,由于它在容器中运行,因此无论您在服务器上运行的发行版如何,它都可以工作。

接下来,将以下块添加到您的 Dockerfile

〜/雷鸟/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 作为基础映像,然后安装在容器中运行 GUI 应用程序所需的最低限度。 请注意,您将 apt-get update 作为同一指令的一部分运行,以防止 Docker 出现缓存问题。 为了节省空间,您还删除了之后下载的包列表(缓存的包本身被 默认删除 )。 您还在创建 /usr/share/desktop-directories 因为某些应用程序依赖于现有目录。

让我们添加另一个小代码块:

〜/雷鸟/Dockerfile

...
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 站点)。

现在,我们可以添加主应用程序的说明:

〜/雷鸟/Dockerfile

...
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 不支持它。

接下来,让我们开始添加指令以将最后几个文件添加到容器中:

〜/雷鸟/Dockerfile

...
COPY --from=easy-novnc-build /bin/easy-novnc /usr/local/bin/
COPY menu.xml /etc/xdg/openbox/
COPY supervisord.conf /etc/
EXPOSE 8080

在这里,您将之前创建的配置文件添加到映像中,并从第一阶段复制 easy-novnc 二进制文件。

下一个代码块创建数据目录并为您的应用程序添加一个专用用户。 这很重要,因为某些应用程序拒绝以 root 身份运行。 即使在容器中也不以 root 身份运行应用程序也是一种很好的做法。

〜/雷鸟/Dockerfile

...
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。 您还在数据目录上安装了一个卷,以确保它在重新启动之间保持不变。

最后,让我们添加启动所有内容的说明:

〜/雷鸟/Dockerfile

...
CMD ["sh", "-c", "chown app:app /data /dev/stdout && exec gosu app supervisord"]

通过将默认命令设置为 supervisord,管理器将启动运行应用程序所需的进程。 在这种情况下,您使用的是 CMD 而不是 ENTRYPOINT。 在大多数情况下,它不会产生影响,但出于几个原因,使用 CMD 更适合此目的。 首先,supervisord 不接受任何与我们相关的参数,如果您向容器提供参数,它们将替换 CMD 并附加到 ENTRYPOINT。 其次,使用 CMD 允许我们在向容器传递参数时提供完全不同的命令(将由 /bin/sh -c 执行),这使得调试更容易。

最后,您需要在启动supervisord之前以root身份运行chown,以防止数据卷的权限问题并允许子进程打开stdout。 这也意味着您需要使用 gosu 而不是 USER 指令来切换用户。

这是您完成的 Dockerfile 的样子:

〜/雷鸟/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 --from=easy-novnc-build /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 — 一个 GUI 应用程序。

第 4 步 - 构建和运行容器

下一步是构建您的容器并将其设置为在启动时运行。 您还将设置一个卷以在重新启动和更新之间保留应用程序数据。

首先构建你的容器。 确保在 ~/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

请注意,如果需要,可以将 --name 选项后的 thunderbird-app 替换为不同的名称。 无论您选择什么,您的应用程序现在都已容器化并正在运行。 现在让我们使用 Caddy Web 服务器来保护它并远程连接到它。

第 5 步 — 设置 Caddy

在此步骤中,您将设置 Caddy Web 服务器以提供身份验证,并可选择通过 WebDAV 进行远程文件访问。 为简单起见,并允许您将它与现有的反向代理一起使用,您将在另一个容器中运行它。

创建一个新目录,然后在其中移动:

mkdir ~/caddy
cd ~/caddy

现在使用 nano 或您喜欢的编辑器创建一个新的 Dockerfile

nano ~/caddy/Dockerfile

然后添加以下指令:

~/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.1.1' >> 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 --from=caddy-build /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 在启用 WebDAV 插件的情况下构建 Caddy,然后在 /etc/CaddyfileCaddyfile 端口 8080 上启动它。 保存并关闭文件。

接下来,您将配置 Caddy Web 服务器。 在刚刚创建的目录中创建一个名为 Caddyfile 的文件:

nano ~/caddy/Caddyfile

现在将以下代码块添加到您的 Caddyfile

〜/球童/球童文件

{
    order webdav last
}
:8080 {
    log
    root * /data
    reverse_proxy thunderbird-app:8080

    handle_path /files/* {         
        file_server browse
    }
    redir /files /files/

    handle /webdav/* {
        webdav {             
            prefix /webdav         
        }
    }
    redir /webdav /webdav/

    basicauth /* {
        {env.APP_USERNAME} {env.APP_PASSWORD_HASH}
    }
}

Caddyfile 将根目录代理到您在步骤 4 中创建的 thunderbird-app 容器(Docker 将其解析为正确的 IP)。 它还将在 /files 上提供基于 Web 的只读文件浏览器,并在 /webdav 上运行 WebDAV 服务器,您可以将其安装在本地以访问您的文件。 用户名和密码从环境变量 APP_USERNAMEAPP_PASSWORD_HASH 中读取。

现在构建容器:

docker build -t thunderbird-caddy .

Caddy v2 要求您散列您想要的密码。 运行以下命令并记住将 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

我们现在可以访问和测试我们的应用程序了。

第 6 步 — 测试和管理应用程序

让我们访问您的应用程序并确保它正常工作。

首先,在网络浏览器中打开http://your_server_ip:8080,使用您之前选择的凭据登录,然后单击连接

您现在应该能够与应用程序交互,并且它应该会自动调整大小以适合您的浏览器窗口。

如果您右键单击黑色桌面,您应该会看到一个允许您访问终端的菜单。 如果您单击鼠标中键,您应该会看到一个窗口列表。

现在在网络浏览器中打开 http://your_server_ip:8080/files/。 您应该能够访问您的文件。

或者,您可以尝试在 WebDAV 客户端中安装 http://your_server_ip:8080/webdav/。 您应该能够直接访问和修改您的文件。 如果您在 Windows 资源管理器中使用 Map network drive 选项,您将需要使用反向代理来添加 HTTPS 或将 HKLM\SYSTEM\CurrentControlSet\Services\WebClient\Parameters\BasicAuthLevel 设置为 DWORD:2

无论哪种情况,您的本机 GUI 应用程序现在都可以远程使用了。

结论

您现在已经成功地为 Thunderbird 设置了一个 Docker 容器,然后使用 Caddy,您已经配置了通过 Web 浏览器访问它。 如果您需要升级您的应用程序,请停止容器,运行 docker rm thunderbird-app thunderbird-web,重新构建映像,然后重新运行上述步骤中的 docker run 命令。 您的数据仍将保留,因为它存储在一个卷中。

如果你想了解更多关于基本 Docker 命令的信息,你可以阅读这个 tutorialthis cheatsheet。 对于长期使用,您可能还需要考虑启用 HTTPS(这需要域)以提高安全性。

此外,如果您要部署多个应用程序,您可能希望使用 Docker Compose 或 Kubernetes,而不是手动启动每个容器。 请记住,本教程可以作为在您的服务器上运行任何其他 Linux 应用程序的基础,包括:

  • Wine,用于在 Linux 上运行 Windows 应用程序的兼容层。
  • GIMP,一个开源的图像编辑器。
  • Cutter,开源逆向工程平台。

最后一个选项展示了容器化和远程访问 GUI 应用程序的巨大潜力。 通过此设置,您现在可以使用计算能力比本地更多的服务器来运行 Cutter 等资源密集型工具。