如何解决docker-compose nginx proxy_pass 到上游容器的行为不符合预期
我正在尝试使用一个基本的反向代理来处理基于 [本教程][1] 的多个网站,但要对其进行调整以使用 单个 docker-compose 文件 和 proxy_pass 到上游容器。这似乎是最简洁的方法,因为它适用于我的学习/测试服务器,我将经常启动和停止容器。我想在开始添加更复杂的应用程序容器之前锁定它。我不确定我应该转发端口的配置的哪一部分,因为网上的大多数问题和教程都没有使用上游容器。
编辑 - 默认服务器未在 443 上侦听,修复此问题消除了一个混乱。现在我只能从 x.x.x.x/
获取预期的 index.html 以及来自 x.x.x.x/site1
或 x.x.x.x/site2
(或其他任何东西)
据我所知,只要容器是链接的(在同一个 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-compose
的 version: '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
文件或自己伪造请求标头):
- 请求、HTTP 或 HTTPS 到达
site1
块 - 它被代理到
http://docker-site1/
(http) -
docker-site1
被解析为只包含一台服务器的服务器组:website1-container:8081
- 容器
site1_app
在其8081
端口(不是443
)接收请求。 - 即使 确实如此,
site1_app
也可能希望在443
端口上使用 HTTPS。
所以你应该:
- 使用内部端口而不是外部端口,
- 检查您是否将 HTTP(相应的 HTTPS)发送到等待 HTTP(相应的 HTTPS)的端口
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。