繁体   English   中英

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

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

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

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

从我读过,端口内部由码头工人,只要处理的容器链接(同泊坞窗网络上),并且不需要甚至暴露声明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": ""
        }
    },
    "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

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

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

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

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

[..] 当我请求服务器的根 xxxx 时,我得到 website1,当我请求 xxxx/site1 时,我得到 404 错误。

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

从未得到网站的回应2

您需要请求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 (或xxxx: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可能想HTTPS上的443端口。

所以你应该:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM