微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

docker-compose nginx proxy_pass 到上游容器的行为不符合预期

如何解决docker-compose nginx proxy_pass 到上游容器的行为不符合预期

我正在尝试使用一个基本的反向代理来处理基于 [本教程][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.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

./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": ""
        }
    },"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

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

解决方法

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

您可以在上游定义中执行此操作(摘自下面的 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。) >

从未收到 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,您将从site1_app(或localhost:8081)访问x.x.x.x:8081的443端口
  • 同一网络上的另一个容器,您将从site1_app*(或site1_app:443)访问https://site1_app的443端口

(让我们想象一下 site1_app 也监听端口 80):

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。