代码之家  ›  专栏  ›  技术社区  ›  rubund

在通过SSH连接的服务器上,在Docker容器中可靠地运行X应用程序,无需“-net host”

  •  40
  • rubund  · 技术社区  · 6 年前

    如果没有Docker容器,使用SSH X11转发在远程服务器上运行X11程序很简单( ssh-X ). 当应用程序在服务器上的Docker容器中运行时,我也尝试了同样的方法。当使用-X选项SSH连接到服务器时,会设置一个X11隧道,并且环境变量“$DISPLAY”通常会自动设置为“localhost:10.0”或类似值。如果我只是尝试在Docker中运行X应用程序,则会出现以下错误:

    Error: GDK_BACKEND does not match available displays
    

    我的第一个想法是使用“-e”选项将$DISPLAY实际传递到容器中,如下所示:

    docker run -ti -e DISPLAY=$DISPLAY name_of_docker_image
    

    这有帮助,但不能解决问题。错误消息更改为:

    Unable to init server: Broadway display type not supported: localhost:10.0
    Error: cannot open display: localhost:10.0
    

    在网上搜索之后,我发现我可以做一些 xauth公司 修复身份验证的魔法。我添加了以下内容:

    SOCK=/tmp/.X11-unix
    XAUTH=/tmp/.docker.xauth
    xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge -
    chmod 777 $XAUTH
    docker run -ti -e DISPLAY=$DISPLAY -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH \ 
      -e XAUTHORITY=$XAUTH name_of_docker_image
    

    但是,这仅在还添加“时才起作用” --网络主机 “对于docker命令:

    docker run -ti -e DISPLAY=$DISPLAY -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH \ 
      -e XAUTHORITY=$XAUTH --net host name_of_docker_image
    

    这是不可取的,因为它使整个主机网络对容器可见。

    为了让它在docker中的远程服务器上完全运行而不使用“-net主机”,现在缺少什么?

    4 回复  |  直到 6 年前
        1
  •  50
  •   Ben rubund    5 年前

    我想出来了。使用SSH和X11转发连接到计算机时, /tmp/。X11 unix 不用于X通信,不需要与$XSOCK相关的部分。

    任何X应用程序都使用$DISPLAY中的主机名,通常是“localhost”,并使用TCP进行连接。然后通过隧道将其传回SSH客户端。当为Docker使用“-net host”时,Docker容器的“localhost”与Docker主机的“localhost”相同,因此它可以正常工作。

    如果未指定“-net host”,Docker将使用默认网桥网络模式。 这意味着“localhost”在容器中的含义与主机不同 ,容器中的X应用程序将无法通过引用“localhost”来查看X服务器。因此,为了解决这个问题,必须用主机的实际IP地址替换“localhost”。通常为“172.17.0.1”或类似值。检查“docker0”接口的“ip地址”。

    这可以通过sed替换完成:

    DISPLAY=`echo $DISPLAY | sed 's/^[^:]*\(.*\)/172.17.0.1\1/'`
    

    此外,SSH服务器通常未配置为接受到此X11隧道的远程连接。然后必须通过编辑进行更改 /etc/ssh/sshd\u配置 (至少在Debian中)和设置:

    X11UseLocalhost no
    

    然后重新启动SSH服务器,并使用“SSH-X”重新登录到服务器。

    差不多了,但还有一个并发症。如果Docker主机上正在运行任何防火墙,则必须打开与X11隧道关联的TCP端口。端口号是两个端口之间的数字 : 以及 . 在$显示中添加到6000。

    要获取TCP端口号,可以运行:

    X11PORT=`echo $DISPLAY | sed 's/^[^:]*:\([^\.]\+\).*/\1/'`
    TCPPORT=`expr 6000 + $X11PORT`
    

    然后(如果使用 ufw公司 作为防火墙),为172.17.0.0子网中的Docker容器打开此端口:

    ufw allow from 172.17.0.0/16 to any port $TCPPORT proto tcp
    

    所有命令都可以放在一个脚本中:

    XSOCK=/tmp/.X11-unix
    XAUTH=/tmp/.docker.xauth
    xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | sudo xauth -f $XAUTH nmerge -
    sudo chmod 777 $XAUTH
    X11PORT=`echo $DISPLAY | sed 's/^[^:]*:\([^\.]\+\).*/\1/'`
    TCPPORT=`expr 6000 + $X11PORT`
    sudo ufw allow from 172.17.0.0/16 to any port $TCPPORT proto tcp 
    DISPLAY=`echo $DISPLAY | sed 's/^[^:]*\(.*\)/172.17.0.1\1/'`
    sudo docker run -ti --rm -e DISPLAY=$DISPLAY -v $XAUTH:$XAUTH \
       -e XAUTHORITY=$XAUTH name_of_docker_image
    

    假设您不是root,因此需要使用sudo。

    而不是 sudo chmod 777 $XAUTH ,您可以运行:

    sudo chown my_docker_container_user $XAUTH
    sudo chmod 600 $XAUTH
    

    如果服务器上的其他用户知道您创建了/tmp/的内容,则他们也无法访问X服务器。docker。的身份验证文件。

    我希望这能使它在大多数情况下正常工作。

        2
  •  2
  •   RexBarker    4 年前

    在我的情况下,我坐在“remote”并连接到“docker\u host”上的“docker\u容器”:

    远程-->docker\u主机-->docker\u容器

    为了使VScode更容易调试脚本,我将SSHD安装到“docker\u容器”中,报告端口22,映射到“docker\u主机”上的另一个端口(比如1234)。

    因此,我可以通过ssh(从“remote”)直接连接正在运行的容器:

    ssh -Y -p 1234 appuser@docker_host.local

    (其中 appuser 是“docker\u容器”中的用户名。我现在正在处理本地子网,因此我可以通过引用我的服务器。本地映射。对于外部IP,只需确保路由器已映射到此计算机的此端口。)

    这将通过ssh直接创建从“remote”到“docker\u container”的连接。

    远程-->(ssh)-->docker\u容器

    在“docker\u容器”中,我安装了 sshd 具有 sudo apt-get install openssh-server (您可以将其添加到Dockerfile中,以便在构建时安装)。

    要允许X11转发工作,请编辑 /etc/ssh/sshd_config 文件本身:

    X11Forwarding yes
    X11UseLocalhost no
    

    然后在容器中重新启动ssh。您应该从“docker\u主机”将shell执行到容器中,而不是通过ssh连接到“docker\u容器”时:( docker exec -ti docker_container bash )

    重新启动sshd: sudo service ssh restart

    当您通过ssh连接到“docker\u容器”时,请检查 $DISPLAY 环境变量。应该是这样的

    appuser@3f75a98d67e6:~/data$ echo $DISPLAY
    3f75a98d67e6:10.0
    

    通过ssh(如cv2.imshow())从“docker\u container”中执行您最喜欢的X11图形程序进行测试

        3
  •  2
  •   dyp    4 年前

    如果您设置 X11UseLocalhost = no ,你甚至允许 到达X11插座的外部通信量 . 也就是说,定向到机器外部IP的流量可以到达SSHD X11转发。还有两种安全机制 可以 应用(防火墙,X11身份验证)。尽管如此,我还是宁愿离开 系统全局设置 如果您正在处理用户问题,甚至是像本例中这样的特定于应用程序的问题,请单独使用。


    这里有一个替代改变的方法 X11UseLocalhost 在sshd配置中:

                                               + docker container net ns +
                                               |                         |
               172.17.0.1                      |   172.17.0.2            |
            +- docker0 --------- veth123@if5 --|-- eth0@if6              |
            |  (bridge)          (veth pair)   |   (veth pair)           |
            |                                  |                         |
            |  127.0.0.1                       +-------------------------+
    routing +- lo
            |  (loopback)
            |
            |  192.168.1.2
            +- ens33
               (physical host interface)
    

    使用默认值 X11UseLocalhost yes ,sshd侦听 127.0.0.1 在根网络命名空间上。我们需要获取从docker网络名称空间内部到根网络ns中的环回接口的X11流量。veth对连接到 docker0 因此,桥接器和两端可以与172.17.0.1通信,无需任何路由。根网络ns中的三个接口( docker0 , lo ens33 )可以通过路由进行通信。

    我们希望实现以下目标:

                                               + docker container net ns +
                                               |                         |
               172.17.0.1                      |   172.17.0.2            |
            +- docker0 --------< veth123@if5 --|-< eth0@if6 -----< xeyes |
            |  (bridge)          (veth pair)   |   (veth pair)           |
            v                                  |                         |
            |  127.0.0.1                       +-------------------------+
    routing +- lo >------- sshd -+
               (loopback)        |
                                 v
               192.168.1.2       |
               ens33 ------<-----+
               (physical host interface)
    

    我们可以让X11应用程序直接与 172.17.0.1 要“逃离”docker net ns。通过设置 DISPLAY 适当地: export DISPLAY=172.17.0.1:10 :

                                               + docker container net ns+
                                               |                         |
               172.17.0.1                      |   172.17.0.2            |
               docker0 --------- veth123@if5 --|-- eth0@if6 -----< xeyes |
               (bridge)          (veth pair)   |   (veth pair)           |
                                               |                         |
               127.0.0.1                       +-------------------------+
               lo
               (loopback)
             
               192.168.1.2
               ens33
               (physical host interface)
    

    现在,我们添加了一个iptables规则,以在根网络ns中从172.17.0.1路由到127.0.0.1:

    iptables \
      --table nat \
      --insert PREROUTING \
      --proto tcp \
      --destination 172.17.0.1 \
      --dport 6010 \
      --jump DNAT \
      --to-destination 127.0.0.1:6010
    
    sysctl net.ipv4.conf.docker0.route_localnet=1
    

    也许您可以通过仅从此容器(veth end)路由流量来改进这一点。还有,我不太清楚为什么 route_localnet 老实说,这是必要的。看来 127/8 是数据包的奇怪源/目标,因此默认情况下禁用路由。您可能还可以将流量从docker net ns内的环回接口重新路由到veth对,然后从那里重新路由到根net ns中的环回接口。

    使用上面给出的命令,我们最终得到:

                                               + docker container net ns +
                                               |                         |
               172.17.0.1                      |   172.17.0.2            |
            +- docker0 --------< veth123@if5 --|-< eth0@if6 -----< xeyes |
            |  (bridge)          (veth pair)   |   (veth pair)           |
            v                                  |                         |
            |  127.0.0.1                       +-------------------------+
    routing +- lo
               (loopback)
    
               192.168.1.2
               ens33
               (physical host interface)
    

    但是,现在我们尝试访问X11服务器 172.17.0.1:10 . 在x权限文件中找不到该条目( ~/.Xauthority ),通常是 <hostname>:10 . 根据鲁本的建议,在docker容器中添加一个可见的新条目:

    xauth add 172.17.0.1:10 . <cookie>
    

    哪里 <cookie> cookie是否由SSH X11转发设置,例如通过 xauth list .

    您可能还必须允许流量进入 172.17.0.1:6010 在防火墙中。


    您还可以从docker容器网络命名空间内的主机启动应用程序:

    sudo nsenter --target=<pid of process in container> --net su - $USER <app>
    

    没有 su ,您将以root身份运行。当然,您也可以使用另一个容器并共享网络命名空间:

    sudo docker run --network=container:<other container name/id> ...
    

    上面显示的X11转发机制适用于整个网络名称空间(实际上,适用于连接到 docker0 桥梁)。因此,它将适用于容器网络名称空间内的任何应用程序。

        4
  •  0
  •   ognum    2 年前

    我使用一种完全可以在docker容器中执行的自动化方法。

    只需将显示变量传递给容器,然后安装 .Xauthority . 此外,它只使用DISPLAY变量中的端口,因此在以下情况下也可以使用 DISPLAY=localhost:XY.Z .

    创建文件, source-me.sh ,内容如下:

    # Find the containers address in /etc/hosts
    CONTAINER_IP=$(grep $(hostname) /etc/hosts | awk '{ print $1 }')
    # Assume the docker-host IP only differs in the last byte
    SUBNET=$(echo $CONTAINER_IP | sed 's/\.[^\.]$//')
    DOCKER_HOST_IP=${SUBNET}.1
    
    # Get the port from the DISPLAY variable
    DISPLAY_PORT=$(echo $DISPLAY | sed 's/.*://'  | sed 's/\..*//')
    # Create the correct display-name
    export DISPLAY=$DOCKER_HOST_IP:$DISPLAY_PORT
    
    # Find an existing xauth entry for the same port (DISPLAY_PORT), 
    # and copy everything except the dispay-name
    # filtering out entries containing /unix: which correspond to "same-machine" connections
    ENTRY=$(xauth -n list | grep -v '/unix\:' | grep "\:${DISPLAY_PORT}" | head -n 1 | sed 's/^[^ ]* *//')
    # Prepend our display-name
    ENTRY="$DOCKER_HOST_IP:$DISPLAY_PORT $ENTRY"
    # Add the new xauth entry. 
    # Because our .Xauthority file is mounted, a new file 
    # named ${HOME}/.Xauthority-n will be created, and a warning 
    # is printed on std-err 
    xauth add $ENTRY 2> /dev/null
    # replace the content of ${HOME}/.Xauthority with that of ${HOME}/.Xauthority-n
    # without creating a new i-node.
    cat ${HOME}/.Xauthority-n > ${HOME}/.Xauthority
    

    创建以下Dockerfile以进行测试:

    FROM ubuntu
    RUN apt-get update
    RUN apt-get install -y xauth
    COPY source-me.sh /root/
    RUN cat /root/source-me.sh >> /root/.bashrc
     
    # xeyes for testing:
    RUN apt-get install -y x11-apps
    

    构建和运行:

    docker build -t test-x .
    docker run -ti \
        -v $HOME/.Xauthority:/root/.Xauthority:rw \
        -e DISPLAY=$DISPLAY \
        test-x \
        bash
    

    在容器内,运行:

    xeyes
    

    要以非交互方式运行,必须确保 找到我。上海 来源:

    docker run \
        -v $HOME/.Xauthority:/root/.Xauthority:rw \
        -e DISPLAY=$DISPLAY \
        test-x \
        bash -c "source source-me.sh ; xeyes"
    
    推荐文章