docker-compose nginx proxy_pass 到上游容器不按预期运行

docker-compose nginx proxy_pass to upstream containers not behaving as expected

我正在尝试让一个基本的反向代理工作来处理基于 [本教程][1] 的多个网站,但调整它以使用 单个 docker-将文件和proxy_pass组合到上游容器。这似乎是最简洁的方法,因为它适用于我的学习/测试服务器,我将经常启动和停止容器。我想在开始添加更复杂的应用程序容器之前锁定它。我不确定我应该转发端口的配置的哪一部分,因为大多数在线问题和教程都没有使用上游容器。

编辑 - 默认服务器未在 443 上侦听,修复此问题消除了一个混乱。现在我只从 x.x.x.x/ 获得预期的 index.html 和从 x.x.x.x/site1x.x.x.x/site2 (或其他任何东西)

获得反向代理自定义 404 页面

根据我的阅读,端口由 docker 在内部处理,只要容器是 linked(在同一个 docker 网络上)甚至是 expose 语句在 docker-compose.yml 中不需要,只要容器以 docker-compose up

启动

而且我已经尝试将自定义端口转发到 docker-compose.yml

中的容器
ports:
  - 8081:443

这在 nginx 中 default.conf

upstream docker-site1 {
    server website1-container:8081;
}

但这给了我 502 Bad Gateway

我正在使用命名容器和外部网络来保持名称静态,以努力将容器间网络与主机分开,并利用这方面的 Docker 功能。

我现在已经在这上面花了两天时间,我真的需要一些方向来避免绕圈子!

编辑- 仍在原地转圈。感谢 lmsec 更新了 default.conf,并将 /site1 添加到 docker-compose.yml

中的卷路径

我的 docker-compose.yml(在顶级目录中)已编辑 - 我最好的工作配置

version: '3.6'
services:
  proxy:
    build: ./proxy/
    container_name: reverse-proxy
    hostname: reverse-proxy

    networks:
      - public
      - website1
      - website2

    ports:
      - 80:80
      - 443:443


  site1_app:
    build:
      ./site1/
    volumes:
      - ./site1/html:/usr/share/nginx/html/site1
    container_name: website1-container
    hostname: website1-container
    networks:
      - website1
 
  site2_app:
    build:
      ./site2/
    volumes:
      - ./site2/html:/usr/share/nginx/html/site2
    container_name: website2-container
    hostname: website2-container
    networks:
      - website2

networks:
  public:
    external: true
  website1:
    external: true
  website2:
    external: true

Docker文件在./proxy/

FROM nginx:1.20-alpine

COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY ./backend-not-found.html /var/www/html/backend-not-found.html
COPY ./index.html /var/www/html/index.html

#  Proxy and SSL configurations
COPY ./includes/ /etc/nginx/includes/
# Proxy SSL certificates
COPY ./ssl/ /etc/ssl/certs/nginx/

网站Docker文件仅包含FROM nginx:1.20-alpine

default.conf in ./proxy/ EDITED - 我最有效的配置没有 link JS,CSS,Images

# Default
server {
    # listen on port 80 (http)
    listen 80 default_server;
    server_name _;
    
    location / {
        # redirect any requests to the same URL but on https
        return 301 https://$host$request_uri;
    }
}
    
server {
  listen 443 ssl http2 default_server;

  server_name _;
  root /var/www/html;

  charset UTF-8;

  # Path for SSL config/key/certificate
  ssl_certificate /etc/ssl/certs/nginx/proxy.crt;
  ssl_certificate_key /etc/ssl/certs/nginx/proxy.key;
  include /etc/nginx/includes/ssl.conf;


  error_page 404 /backend-not-found.html;
  location = /backend-not-found.html {
    allow   all;
  }

  location / {
    index index.html;
  }
  location /site1 {
    include /etc/nginx/includes/proxy.conf;
    proxy_pass http://website1-container;
  }
  location /site2 {
    include /etc/nginx/includes/proxy.conf;
    proxy_pass http://website2-container;
  }


  access_log off;
  log_not_found off;
  error_log  /var/log/nginx/error.log error;
}

proxy.conf 在./proxy/includes/

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_request_buffering off;
proxy_http_version 1.1;
proxy_intercept_errors on;

每个网站容器都有自己的网络,并与代理容器共享。

 {
    "Name": "website1",
    "Id": "9477470a8689d08776b38c4315882caff75573b7244f77091aa5e5438804ce36",
    "Created": "2021-06-21T02:52:25.402118801Z",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
        "Driver": "default",
        "Options": {},
        "Config": [
            {
                "Subnet": "192.168.160.0/20",
                "Gateway": "192.168.160.1"
            }
        ]
    },
    "Internal": false,
    "Attachable": false,
    "Ingress": false,
    "ConfigFrom": {
        "Network": ""
    },
    "ConfigOnly": false,
    "Containers": {
        "7c1a8b62864642afd5366ef88d762e4c5450eee02acb8c3f1890444b59379340": {
            "Name": "website1-container",
            "EndpointID": "f04d96343737574ca869270954461774f731851b781120119c21e02c0aa9968e",
            "MacAddress": "02:42:c0:a8:a0:02",
            "IPv4Address": "192.168.160.2/20",
            "IPv6Address": ""
        },
        "a88326952fb5f25f9084eb038f22f56b7331032a5ba71848ea6ada677a2ed998": {
            "Name": "reverse-proxy",
            "EndpointID": "b0c97c7f8dfe0febddbd6668481a009cce0c4f20dae3c3d3280dad0069c90394",
            "MacAddress": "02:42:c0:a8:a0:03",
            "IPv4Address": "192.168.160.3/20",
            "IPv6Address": ""
        }
    },
    "Options": {},
    "Labels": {}
}

我可以通过这个网络访问网站容器,甚至可以使用 curl 获得 index.html: sudo docker exec reverse-proxy curl 192.168.160.2/site1/index.html

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0<!DOCTYPE html>
<html>
  <head>
    <title>Site 1</title>
  </head>
  <body>
    <h1>This is a sample "site1" response</h1>
  </body>
</html>
100   142  100   142    0     0  20285      0 --:--:-- --:--:-- --:--:-- 23666

我将此问题标记为关闭。我得出的结论是 docker 的最新版本在使用 proxy_pass 到 docker 容器时不需要任何特殊的端口转发,尽管如果需要可以在 [=89= 中完成]-compose 和 nginx default.conf - 正如 lmsec 答案所解释的那样。

[..] in which part of the configuration I should be forwarding ports [..] using upstream containers.

您可以在上游定义中进行(摘自下面的 nginx docs):

upstream backend {
    server backend1.example.com       weight=5;
    server backend2.example.com:8080;
    # [..]
}

[..] when I request my server's root x.x.x.x, I get website1 and when I request x.x.x.x/site1 I get a 404 error.

您没有为 https (443) 定义 default_server,因此 第一个服务器用作 443 的默认 。(不确定为什么会得到一个404.)

never got a response from website2

您需要请求 site2 才能得到它的响应(因为 server_name site2;)。为了测试目的,你可以把它放在你的主机文件中。

site1     127.0.0.1
site2     127.0.0.1

这里有一些其他键可以更快地开始 nginx-as-a-proxy :

  • server_name 就像一个请求过滤器;
  • 使用 proxy_pass http://docker-site1/;(尾随 /)以便 /example 转到 http://docker-site1/example,而不是 http://docker-site1 ;
  • 您可以根据 URI 代理到不同的主机或上游 (示例如下:/site2/site3)。
server {
  # Filter requests having 'Host: site1' (ignore the others)
  server_name site1;

  location / {
    # Send everything beginning with '/' to docker-site1
    proxy_pass http://docker-site1/;
  }

  location /site2/ {
    # Send everything beginning with '/site2/' to docker-site2
    #   removing the leading `/site2`
    proxy_pass http://docker-site2/;
  }

  location /site3/ {
    # Send everything beginning with '/site3/' to docker-site3
    #   keeping the leading `/site2`
    proxy_pass http://docker-site3/site3/;
  }
}

server {
  # do something else if the requested Host is site2
  server_name site2;  
}

(当然?)这在没有 upstream 的情况下也有效,您的服务器地址在 proxy_pass 而不是 upstream 标识符中。


编辑 - 奖励:Docker(-compose) 端口和网络

site1_app:
  ports:
    - 8081:443
  • 从“外部”Docker,您将从 localhost:8081(或 x.x.x.x:8081
  • 访问 site1_app 的 443 端口
  • 同一网络 上的另一个容器,您将从 site1_app:443*(或 https://site1_app 访问 site1_app 的 443 端口)

(假设 site1_app 也在端口 80 上侦听):

  • 从“外部站点”Docker,您无法访问 site1_app 的 80 端口:它没有被转发(这里,只有 443 被转发)
  • 同一网络 上的另一个容器,您将从 site1_app:80*(或 http://site1_app 访问 site1_app 的 80 端口)

*不确定这是否适用于 docker-composeversion: '2',但适用于 version: '3.9'

您编写的以下行允许您调用 website1_container 而不是 site1_app :

container_name: website1-container 
hostname: website1-container

因此,如果您这样做:

# 3
upstream docker-site1 {
    server website1-container:8081;
}
server {
  # 1
  listen 80;
  listen 443 ssl http2;
  server_name site1;

  # [..] SSL config/key/certificate

  location / {
    # 2
    proxy_pass http://docker-site1/;
  }

假设您将请求 header 设置为 Host: site1(感谢您的 hosts 文件或您自己伪造请求 header):

  1. HTTP 或 HTTPS 请求到达 site1
  2. 它被代理到 http://docker-site1/ (http)
  3. docker-site1 解析为仅包含一台服务器的服务器组:website1-container:8081
  4. 容器 site1_app 在其 8081 端口(不是 443)上接收请求。
  5. 即使 如果 它做到了,site1_app 可能需要 443 端口上的 HTTPS。

所以你应该:

  1. 使用内部端口而不是外部端口,
  2. 检查您是否将 HTTP(或 HTTPS)发送到等待 HTTP(或 HTTPS)的端口