簡體   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