docker-compose nginx proxy_pass 到上游容器不按预期运行
docker-compose nginx proxy_pass to upstream containers not behaving as expected
我正在尝试让一个基本的反向代理工作来处理基于 [本教程][1] 的多个网站,但调整它以使用 单个 docker-将文件和proxy_pass组合到上游容器。这似乎是最简洁的方法,因为它适用于我的学习/测试服务器,我将经常启动和停止容器。我想在开始添加更复杂的应用程序容器之前锁定它。我不确定我应该转发端口的配置的哪一部分,因为大多数在线问题和教程都没有使用上游容器。
编辑 - 默认服务器未在 443 上侦听,修复此问题消除了一个混乱。现在我只从 x.x.x.x/
获得预期的 index.html 和从 x.x.x.x/site1
或 x.x.x.x/site2
(或其他任何东西)
获得反向代理自定义 404 页面
根据我的阅读,端口由 docker 在内部处理,只要容器是 linked(在同一个 docker 网络上)甚至是 expose 语句在 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
Docker文件在./proxy/
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/
网站Docker文件仅包含FROM nginx:1.20-alpine
default.conf in ./proxy/ EDITED - 我最有效的配置没有 link JS,CSS,Images
# 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.conf 在./proxy/includes/
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 容器时不需要任何特殊的端口转发,尽管如果需要可以在 [=89= 中完成]-compose 和 nginx default.conf - 正如 lmsec 答案所解释的那样。
[..] in which part of the configuration I should be forwarding ports [..] using upstream containers.
您可以在上游定义中进行(摘自下面的 nginx docs):
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
# [..]
}
[..] when I request my server's root x.x.x.x, I get website1 and when I request x.x.x.x/site1 I get a 404 error.
您没有为 https (443) 定义 default_server
,因此 第一个服务器用作 443 的默认 。(不确定为什么会得到一个404.)
never got a response from 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,您将从
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-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/;
}
假设您将请求 header 设置为 Host: site1
(感谢您的 hosts
文件或您自己伪造请求 header):
- HTTP 或 HTTPS 请求到达
site1
块
- 它被代理到
http://docker-site1/
(http)
docker-site1
解析为仅包含一台服务器的服务器组:website1-container:8081
- 容器
site1_app
在其 8081
端口(不是 443
)上接收请求。
- 即使 如果 它做到了,
site1_app
可能需要 443
端口上的 HTTPS。
所以你应该:
- 使用内部端口而不是外部端口,
- 检查您是否将 HTTP(或 HTTPS)发送到等待 HTTP(或 HTTPS)的端口
我正在尝试让一个基本的反向代理工作来处理基于 [本教程][1] 的多个网站,但调整它以使用 单个 docker-将文件和proxy_pass组合到上游容器。这似乎是最简洁的方法,因为它适用于我的学习/测试服务器,我将经常启动和停止容器。我想在开始添加更复杂的应用程序容器之前锁定它。我不确定我应该转发端口的配置的哪一部分,因为大多数在线问题和教程都没有使用上游容器。
编辑 - 默认服务器未在 443 上侦听,修复此问题消除了一个混乱。现在我只从 x.x.x.x/
获得预期的 index.html 和从 x.x.x.x/site1
或 x.x.x.x/site2
(或其他任何东西)
根据我的阅读,端口由 docker 在内部处理,只要容器是 linked(在同一个 docker 网络上)甚至是 expose 语句在 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
Docker文件在./proxy/
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/
网站Docker文件仅包含FROM nginx:1.20-alpine
default.conf in ./proxy/ EDITED - 我最有效的配置没有 link JS,CSS,Images
# 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.conf 在./proxy/includes/
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 容器时不需要任何特殊的端口转发,尽管如果需要可以在 [=89= 中完成]-compose 和 nginx default.conf - 正如 lmsec 答案所解释的那样。
[..] in which part of the configuration I should be forwarding ports [..] using upstream containers.
您可以在上游定义中进行(摘自下面的 nginx docs):
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
# [..]
}
[..] when I request my server's root x.x.x.x, I get website1 and when I request x.x.x.x/site1 I get a 404 error.
您没有为 https (443) 定义 default_server
,因此 第一个服务器用作 443 的默认 。(不确定为什么会得到一个404.)
never got a response from 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,您将从
localhost:8081
(或x.x.x.x:8081
) 访问 - 从 同一网络 上的另一个容器,您将从
site1_app:443
*(或https://site1_app
访问site1_app
的 443 端口)
site1_app
的 443 端口
(假设 site1_app
也在端口 80 上侦听):
- 从“外部站点”Docker,您无法访问
site1_app
的 80 端口:它没有被转发(这里,只有 443 被转发) - 从 同一网络 上的另一个容器,您将从
site1_app:80
*(或http://site1_app
访问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/;
}
假设您将请求 header 设置为 Host: site1
(感谢您的 hosts
文件或您自己伪造请求 header):
- HTTP 或 HTTPS 请求到达
site1
块 - 它被代理到
http://docker-site1/
(http) docker-site1
解析为仅包含一台服务器的服务器组:website1-container:8081
- 容器
site1_app
在其8081
端口(不是443
)上接收请求。 - 即使 如果 它做到了,
site1_app
可能需要443
端口上的 HTTPS。
所以你应该:
- 使用内部端口而不是外部端口,
- 检查您是否将 HTTP(或 HTTPS)发送到等待 HTTP(或 HTTPS)的端口