简单用例:运行Nginx的生产服务器(侦听0.0.0.0:80和443),负责读取SSL证书,并在有效时重定向到Docker容器中的“隐藏”服务。该服务运行Gunicorn(因此是Django网站),并且正在监听8000端口。这听起来像是标准的,简单的,经过测试的,几乎太美了,难以置信……但是,当然,它并没有按照我的意愿工作。
因为运行在其小型Docker容器中的Gunicorn可通过Internet访问。如果转到我的主机名8000端口,则可以访问Gunicorn网站。显然,这很丑陋,但最糟糕的是它完全绕过Nginx和SSL证书。那么,为什么可以通过Internet访问Docker容器?我知道一段时间以来,Docker却是另一种方式。我们需要适当的平衡!
在进一步检查问题时:我确实有防火墙,并且将其配置为极其有限。它仅允许使用端口22(用于ssh,等待重新映射),80和443。因此绝对不应允许使用8000。但是ufw使用iptables,如果容器在该端口上运行,Docker会添加一些iptables规则以绕过配置。
我尝试了很多愚蠢的事情(这是工作的一部分):在我的docker-compose.yml文件中,指定了要绑定的端口,我试图删除它们(如果这样做,nginx无法访问我的隐藏的服务)。我试图添加一个指定绑定的IP(似乎允许):
ports:
- "127.0.0.1:8000:8000"
这产生了一个奇怪的结果,因为Nginx无法连接,但Gunicorn仍然可以通过Internet看到。因此,我要说的与我想要的完全相反。我尝试手动更改Docker服务以添加标志(不好),并尝试在etc/docker/daemon.json
中添加配置文件(再次将“ ip”设置更改为“ 127.0.0.1”)。我没有主意。毕竟,如果有人有指针……我不会认为这是Docker极为罕见的用法。
详细信息:我不直接使用docker run
运行容器。我有一个docker-compose.yml
文件,并使用docker stack
运行服务,所以我蜂拥而至(尽管当时我只有一台机器)。可能是相关的,尽管我再次认为不会。
upstream gunicorn {
server localhost:8000;
}
server {
server_name example.com www.example.com;
location / {
proxy_pass http://gunicorn;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate ...;
ssl_certificate_key ...;
include ...;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = www.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 404; # managed by Certbot
}
docker-compose.yml
version: '3.7'
services:
gunicorn:
image: gunicorn:latest
command: gunicorn mysite.wsgi:application --bind 0.0.0.0:8000
volumes:
- ./:/usr/src/app/
ports:
- "8000:8000"
depends_on:
- db
networks:
- webnet
db:
image: postgres:10.5-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
networks:
- webnet
networks:
webnet:
volumes:
postgres_data:
注意:gunicorn图像是预先构建的,但没有窍门,只是一个Python-3.7:slim图像,所有图像都设置为gunicorn,而Django网站位于mysite /下。它没有暴露任何端口(不是我认为这有什么区别)。
答案 0 :(得分:1)
好吧,经过一番挖掘,这就是我所发现的:docker将确保创建iptables规则以从网络外部访问容器。要求Docker完全不关心iptables并不是一个好策略,因为它将需要它将容器之间的连接转发到外界。因此,documentation建议在docker-user链中创建iptables规则,以防止外部访问Docker守护程序。那就是我所做的。当然,给定的命令(稍作修改以完全禁止外部访问)并没有持久化,因此我不得不创建一个服务来添加此规则。可能不是最好的选择,因此,如果您有更好的选择,请不要犹豫。这是我添加的iptables规则:
iptables -I DOCKER-USER -i ext_if ! -s 127.0.0.1 -j DROP
此规则禁止从外部访问Docker守护程序,但仍允许连接到各个容器。这条规则似乎并没有为我解决任何问题,因为此解决方案并不能完全解决我的问题。为了禁止从Internet访问运行在端口8000上的Docker容器,我在同一脚本中添加了另一个规则:
iptables -I DOCKER-USER -p tcp --destination-port 8000 -j DROP
该规则有点极端:它完全禁止Docker网络上端口8000上的网络通信。主机(localhost)不在此规则之内,因为无论如何,以前的规则都应允许本地流量(如果您手动进行iptables配置,则必须添加此规则,这不是给定的,这是我切换的原因更简单的解决方案,例如ufw
)。如果使用iptables -L
查看防火墙规则,则会看到链DOCKER-USER
在任何Docker规则之前被调用。您可能会发现另一条允许同一端口上的流量的规则。但是,由于我们在更高优先级的规则中禁止流量,因此端口8000将有效地从外部隐藏。
此解决方案似乎可以解决问题,但并不完全优雅且直观。再一次,我不禁怀疑我是否是第一个使用Docker托管我的“隐藏”服务,同时又在前面维护其他服务的人。我猜通常的方法是将nginx放在Docker容器本身中,但是这给我带来了其他问题,我坦率地认为优势已经不存在了。