为什么一些 Nginx 反向代理主机试图从 /etc/nginx/html 本地获取文件?

Why are some Nginx reverse proxied hosts trying to get files locally from /etc/nginx/html?

我已经使用“官方图像”Docker 容器设置了一个简单的 Nginx 反向代理。我已将其设置为充当三个容器的前端,但只有一个工作正常。其他人加载他们的 index.html,但随后尝试使用 URL 拉取辅助文件,使 Nginx 认为它们是本地文件。

Docker 主持人叫奥托。我有 Docker 容器用于 Home Assistant 侦听端口 8123、Statping 端口 8080 和端口 9000 的 Portainer。因为我厌倦了记住端口号,所以我的 proxy_pass 设置为 http:/ /otto/homeassistant/ 重定向到 http://otto:8123/,http://otto/statping/ 重定向到 http://otto:8080/,http://otto/portainer/ 重定向到 http://奥托:9000

但是,只有 Portainer 有效。另外两个加载他们的 index.html 就好了,但随后开始尝试从 /js/somefilename.js 中提取 javascript 文件。这当然失败了,因为前面没有 http://otto/homeassistant 或 http://otto/statping。 Nginx 然后尝试在 /etc/nginx/html 中本地查找文件,失败并放弃。

我的问题是,为什么 Portainer 的工作如此完美,而其他两个甚至无法加载主页?

我试过通过注释掉 location / { } 来禁用本地文件服务,但这没关系。

我试过分配我在其他示例中看到的 HTTP headers,如下所示:

    location /statping/ {
        proxy_pass http://otto:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
    }

但是没有效果

运行 每个 URL 上的 curl 命令会导致每个新位置的“301 永久移动”。会不会是简单的东西,比如 Portainer 更适合反向代理而其他的不适合?

我觉得我一定做对了,因为 Portainer 正在工作,但我看不出我需要做什么才能让其他的工作。我也不完全确定问题是出在 Nginx 配置上,还是出在家庭助理或统计容器上。

如有任何想法,我们将不胜感激。

这是我的 nginx 配置,替换了交付的 default.conf:

pi@otto:~/docker/nginx $ cat rproxy.conf
# Configuration to serve static files and do reverse proxy.
server {
    listen       80;
    listen  [::]:80;
    server_name  otto;

    #access_log  /var/log/nginx/host.access.log  main;

    # Reverse Proxy Configuration
    location /homeassistant/ {
        proxy_pass http://otto:8123/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
    }

    location /portainer/ {
        proxy_pass http://otto:9000/;
    }

    location /statping/ {
        proxy_pass http://otto:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
    }

    # Also serve up static HTML locally.
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }
}

这是启动 Nginx 容器的 docker 运行 命令。

pi@otto:~/docker/nginx $ cat run.sh
#!/bin/bash

if ! docker ps | grep nginx; then
  docker run -d \
    -p 80:80 \
    --hostname nginx \
    --name nginx \
    --restart unless-stopped \
    -v /home/pi/docker/nginx/rproxy.conf:/etc/nginx/conf.d/default.conf \
    -v /home/pi/docker/nginx/content:/usr/share/nginx/html:ro \
    nginx
fi

以下是 curl 命令的结果:

pi@otto:~/docker/nginx $ curl -I http://otto/homeassistant
HTTP/1.1 301 Moved Permanently
Server: nginx/1.19.7
Date: Tue, 16 Mar 2021 02:14:15 GMT
Content-Type: text/html
Content-Length: 169
Location: http://otto/homeassistant/
Connection: keep-alive

pi@otto:~/docker/nginx $ curl -I http://otto/statping
HTTP/1.1 301 Moved Permanently
Server: nginx/1.19.7
Date: Tue, 16 Mar 2021 02:14:40 GMT
Content-Type: text/html
Content-Length: 169
Location: http://otto/statping/
Connection: keep-alive

pi@otto:~/docker/nginx $ curl -I http://otto/portainer
HTTP/1.1 301 Moved Permanently
Server: nginx/1.19.7
Date: Tue, 16 Mar 2021 02:15:03 GMT
Content-Type: text/html
Content-Length: 169
Location: http://otto/portainer/
Connection: keep-alive

这里的日志条目显示了 Home Assistant 和 Statping 尝试在本地查找文件但失败的位置,以及成功的 Portainer。

192.168.0.45 - - [16/Mar/2021:00:30:11 +0000] "GET /homeassistant/ HTTP/1.1" 200 3307 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0" "-",
192.168.0.45 - - [16/Mar/2021:00:30:11 +0000] "GET /frontend_latest/core.a3d9350b.js HTTP/1.1" 404 153 "http://otto/homeassistant/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0" "-",
03/16 00:30:11 [error] 24#24: *1 open() "/etc/nginx/html/frontend_latest/core.a3d9350b.js" failed (2: No such file or directory), client: 192.168.0.45, server: otto, request: "GET /frontend_latest/core.a3d9350b.js HTTP/1.1", host: "otto", referrer: "http://otto/homeassistant/",
...
192.168.0.45 - - [16/Mar/2021:00:31:12 +0000] "GET /statping/ HTTP/1.1" 200 3271 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0" "-",
2021/03/16 00:31:13 [error] 24#24: *3 open() "/etc/nginx/html/js/bundle.js" failed (2: No such file or directory), client: 192.168.0.45, server: otto, request: "GET /js/bundle.js HTTP/1.1", host: "otto", referrer: "http://otto/statping/",
...
192.168.0.45 - - [16/Mar/2021:00:31:26 +0000] "GET /portainer/ HTTP/1.1" 200 23180 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0" "-",
192.168.0.45 - - [16/Mar/2021:00:31:26 +0000] "GET /portainer/api/settings/public HTTP/1.1" 200 533 "http://otto/portainer/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0" "-",
192.168.0.45 - - [16/Mar/2021:00:31:26 +0000] "GET /portainer/api/status HTTP/1.1" 200 20 "http://otto/portainer/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0" "-",

问题正如@zigarn 在评论中指出的那样。被反向代理的应用依赖于以 / 为基础的 URLs,而不是像 /statping 或 /homeassistant 这样的东西。 Portainer 工作是因为它显然没有相同的依赖性。

解决方案是不再使用基于 URL 的反向代理,而是使用基于主机名的反向代理。所以现在,而不是 http://otto.local/statping/, I have http://statping.otto.local/ 所有反向代理应用程序都使用这种安排加载他们的主页,所以我觉得这是一个胜利。

homeassistant 和 node-red 仍然存在一些小问题,我怀疑这可能是由于需要反向代理网络套接字。仍在研究中,但让他们全部拉出主页或登录页面的主要 objective 已经完成。

现在是血淋淋的细节...

因为这是家庭设置,具有标准的基本功能 Internet 路由器,所以在解析 statping.otto.local 或 homeassistant.otto.local 等名称之前,我不得不做一些工作来设置 DNS 服务器。

为此,我在 otto 上安装了 bind 9,同一主机 运行 Docker 容器。它是从 Raspberry Pi OS 上的包安装的,而不是作为容器安装的。诀窍是在 otto.local.

的 DNS 条目之外使用通配符 CNAME

将 *.otto.local 指向 otto.local,我可以得到 {appname}.otto.local 的任意组合来解析 otto 的 IP 地址。现在,输入 http://portainer.otto.local 等。让我使用 otto 和 Nginx。

在 Nginx 配置中,创建几个条目如下所示:

server {
    server_name statping.otto.local;
    location / {
        proxy_pass http://192.168.0.23:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

每个服务器 { } 块都有一个 server_name 和 {appname}.otto.local 命名约定。位置是 /,就像应用程序期望的那样,这有很大帮助。

proxy_pass 指向 otto 的 IP 地址,因为即使 otto 将使用自己的 DNS 服务,Docker 容器也会从 Internet 路由器中提取 DHCP 地址,后者会为域名系统。 (我也没有心情构建 DHCP 基础设施,所以我使用了 IP。)

proxy_set_header 变量是出于纯粹的货物崇拜心态。我不知道它们是否使事情变得更好,但许多配置示例都使用了它们,所以我想我也应该这样做。

因此,总而言之,使用服务器名称是一种比使用 URL 更一致的应用反向代理方式。但是,它并非没有自己的缺点,也不是那么容易,因为它需要比大多数互联网路由器提供的更多的 DNS 控制。

实现我不必记住端口号的最初目标的另一种方法是使用 Nginx 重定向。例如,像这样的块:

server {
    server_name portainer.otto.local;
    return 301 http://portainer.otto.local:9000;
}

每当浏览器连接到端口 80 上的 portainer.otto.local 时,Nginx 会回复 301(永久移动)并提供 portainer.otto.local:9000 作为新目的地。

这似乎是我尝试过的所有选项中效果最好的。它确实需要一个可以处理通配符的 DNS 系统,因此需要做一些额外的工作。它也不会像反向代理那样隐藏后端端口。但对于家庭设置,这是我找到的最简单的方法。