【问题标题】:docker-compose nginx proxy_pass to upstream containers not behaving as expecteddocker-compose nginx proxy_pass 到上游容器的行为不符合预期
【发布时间】:2021-06-23 09:38:39
【问题描述】:

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

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

根据我的阅读,只要容器是链接的(在同一个 docker 网络上),端口就由 docker 在内部处理,而且只要容器以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,并且还在 docker-compose.yml 的卷路径中添加了 /site1

我的 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

./proxy/中的Dockerfile

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/

网站 Dockerfiles 仅包含 FROM nginx:1.20-alpine

./proxy/ 中的 default.conf 已编辑 - 我最常用的配置,不链接 JS、CSS、图像

# 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/includes/中的proxy.conf

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 容器时不需要任何特殊的端口转发,尽管如果需要,可以在 docker-compose 和 nginx default.conf 中完成 - 正如 lmsec 回答所解释的那样。

【问题讨论】:

    标签: docker nginx docker-compose reverse-proxy


    【解决方案1】:

    [..] 我应该在配置的哪个部分转发端口 [..] 使用上游容器。

    您可以在上游定义中执行此操作(摘自以下nginx docs):

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

    [..] 当我请求服务器的根目录 x.x.x.x 时,我得到 website1,当我请求 x.x.x.x/site1 时,我得到一个 404 错误。

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

    从未得到网站2的回复

    您需要请求site2 才能得到它的响应(因为server_name site2;)。出于测试目的,您可以将其放入您的 hosts 文件中。

    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/;
      }
    
    

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

    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)的端口

    【讨论】:

    • 谢谢,这有助于我理解 nginx 配置。我可以通过将它们放在默认服务器的位置块中来访问我的网站。我删除了 site1 和 site2 服务器部分,并将 443 & ssl 配置添加到默认服务器。这样就不需要在上游配置中指定端口......但是,仅将端口 443 添加到上游将拒绝 80/http 请求。我仍然需要在 nginx 配置中获得单独的服务器块才能工作....
    • 编辑,我之前接受了这个答案,因为我的浏览器缓存让我认为一切正常。不是。我仍然无法让服务器块在 nginx default.conf 中工作:(
    • 再次感谢 lmsec,我愿意接受您的回答,因为它确实使我的测试设置正常工作,但是在 docker 容器端口方面我仍然不明智。也许我应该重新提出我的问题?
    • 很高兴知道它有帮助,我添加了一些关于 Docker 端口转发的说明。
    • 谢谢你,我正在努力解决这个问题......所以只是为了澄清,我是否更正了? 1.我不需要上游,除非我想要一些特别的东西,我可以简单地将proxy_pass传递给容器吗? 2. 按照我的意图,我不需要在不同的端口上转发 80 或 443,因为 docker 允许容器在同一个端口上侦听,就像 docker 网络上的“服务器”一样?
    猜你喜欢
    • 1970-01-01
    • 2018-07-08
    • 1970-01-01
    • 2015-03-17
    • 2022-01-22
    • 2023-02-26
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    相关资源
    最近更新 更多