我有一个通过 docker-compose 文件创建的新应用程序。该文件包含 2 个网络:
version: '2.1'
# ----------------------------------
# Services
# ----------------------------------
services:
application:
image: tianon/true
volumes:
- ${APPLICATION_PATH}:/var/www
nginx:
build:
context: ./docker/nginx
volumes_from:
- application
volumes:
- ${DOCKER_STORAGE}/nginx-logs:/var/log/nginx
- ${NGINX_SITES_PATH}:/etc/nginx/sites-available
ports:
- "${NGINX_HTTP_PORT}:80"
- "${NGINX_HTTPS_PORT}:443"
networks:
- frontend
- backend
redis:
build:
context: ./docker/redis
volumes:
- ${DOCKER_STORAGE}/redis:/data
ports:
- "${REDIS_PORT}:6379"
networks:
- backend
# ----------------------------------
# Networks
# ----------------------------------
networks:
frontend:
driver: "bridge"
backend:
driver: "bridge"
# ----------------------------------
# Volumes
# ----------------------------------
volumes:
redis:
driver: "local"
你会注意到我这里有 2 个网络,frontend
和backend
。问题很简单:
- 我如何
frontend
向世界公开,但允许backend
服务相互通信而不向世界公开?(我假设使用 iptables,或者在 docker 中指定我想要向主机公开哪个网络) - 这是一个简单的 1 服务器设置(Digital Ocean),运行 Ubuntu 18.04 LTS
- 在上面的例子中,我应该能够从外部通过端口 80 或 443 访问 nginx,但不能访问 redis。Nginx 容器应该能够在内部访问 redis 服务 6379。
- 更清楚一点:我现在的目的是让后端服务暂时相互通信,因此删除 EXPOSE会工作现在,但最终,我想划分这两个网络。对于后端服务,我想限制 IP 范围(允许其他一些非 docker 网络进行通信),而对于前端服务,我想向全世界开放端口。
为了赏金:docker 中显然有一种方法可以定义多个覆盖网络。我曾以为,将一个网络暴露给外界很简单。我正在尝试在 docker 或 iptables 中找到一种简单的方法来做到这一点。当前的答案给了我一些指导,但需要为每个端口设置手动规则。我想要一个关于如何保护面向“前端”的网络的正确答案,而无需在 iptables 中指定特定端口。
答案1
为了完成@BMitch 对您的需求的回答..
您可以使用额外的容器间网络(或多个,如果您认为有必要或有用例的话)来实现这一点,因为要使此类设置可靠地工作,必须为每个容器分配一个非内部网络。否则,您无法真正确定将为映射端口上的传入流量选择哪个桥接网络(或至少它实际上不可配置)。
显示此类设置的示例撰写文件:
version: '3.6'
services:
c1:
container_name: c1
image: "centos:latest"
entrypoint: /bin/bash -c "while sleep 10; do sleep 10; done"
ports:
- "5000:5000"
networks:
- front
- inter
c2:
container_name: c2
image: "centos:latest"
entrypoint: /bin/bash -c "while sleep 10; do sleep 10; done"
ports:
- "5001:5001"
networks:
- inter
c3:
container_name: c3
image: "centos:latest"
entrypoint: /bin/bash -c "while sleep 10; do sleep 10; done"
ports:
- "5002:5002"
networks:
- back
- inter
c4:
container_name: c4
image: "centos:latest"
entrypoint: /bin/bash -c "while sleep 10; do sleep 10; done"
ports:
- "5003:5003"
networks:
- back
- inter
networks:
front:
name: front
driver: bridge
driver_opts:
com.docker.network.bridge.name: dockerfront
back:
name: back
driver: bridge
driver_opts:
com.docker.network.bridge.name: dockerback
inter:
name: inter
driver: bridge
internal: true
driver_opts:
com.docker.network.bridge.name: dockerinter
它为您的公共服务创建一个桥接器 ([docker]front),为您的后端服务创建一个桥接器 ([docker]back),以及一个内部桥接器(即使请求也不会发布端口,因此可以安全地添加为附加网络)([docker]intern),并将它们分配给容器。
实际上,c2 将无法从外部通过端口 5001 访问,因此可以省略该容器的端口。这只是显示结果:
# iptables -nvL DOCKER -t nat
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- dockerback * 0.0.0.0/0 0.0.0.0/0
0 0 RETURN all -- dockerfront * 0.0.0.0/0 0.0.0.0/0
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
0 0 DNAT tcp -- !dockerback * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5002 to:172.27.0.2:5002
0 0 DNAT tcp -- !dockerfront * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 to:172.25.0.2:5000
0 0 DNAT tcp -- !dockerback * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5003 to:172.27.0.3:5003
dockerback
现在您可以将桥的访问规则添加到DOCKER-USER
链中:
# iptables -I DOCKER-USER ! -s 10.0.0.0/24 -o dockerback -j DROP
# iptables -nvL DOCKER-USER
Chain DOCKER-USER (1 references)
pkts bytes target prot opt in out source destination
3 156 DROP all -- * dockerback !10.0.0.0/24 0.0.0.0/0
10 460 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
注意:当前版本 19.03.3 存在DOCKER-USER
未创建链的错误,该错误已在 19.03.4 中修复,并将很快发布。如果您拥有该版本,则可以自行添加链(每次重新启动 docker 守护程序后都必须执行此操作):
iptables -N DOCKER-USER
iptables -I FORWARD -j DOCKER-USER
iptables -A DOCKER-USER -j RETURN
可能你会找到更适合这些规则的地方,但这是docker建议的方式。(我可以考虑固定网桥的网络,并将规则放入通用转发规则中,该规则不适用于重新启动、接口重新创建等情况下的docker操作。)
此外(或者相反),您可以在机器上使用额外的 IP 将后端服务绑定到另一个 IP,然后将前端服务绑定到该级别并进行过滤。此外,这意味着与上面完全相同的 docker 设置,但另外指定com.docker.network.bridge.host_binding_ipv4
驱动程序选项(这只是已发布端口的默认 IP) - 或者只使用一个网络并根据服务类型指定绑定地址。然后在 DOCKER-USER 中或任何适当的位置阻止 FORWARD,并使用 conntrack 模块 ctorigdst 匹配传入接口。
答案2
Docker 不向外界公开任何网络,docker 网络用于允许容器相互通信。EXPOSE
也不会修改与外界的通信,甚至不会修改容器之间的通信,它只是元数据,最常用作图像创建者和部署容器的个人之间的文档。
允许与外界通信的方法是发布端口,这会创建一个从 docker 主机网络命名空间到容器监听端口的端口转发。因此,为了实现您的目标,您应该只发布您希望外部可访问的端口。然后使用 docker 网络来控制容器之间的通信。
如果你想要发布一个端口并且使用 iptables 限制对该端口的访问,那么使用DOCKER-USER 链是推荐的方法。请注意,我最近看到几个版本都修补了此行为,因此如果您朝这个方向发展,请密切关注发行说明和未解决的问题。还请注意,链是在为容器操作数据包后运行的,因此具有容器目标端口,而不是主机目标端口。要按主机端口进行过滤,您需要使用 conntrack,例如:
iptables -I DOCKER-USER -i eth0 -s 10.0.0.0/24 -p tcp \
-m conntrack --ctorigdstport 8080 -j ACCEPT
iptables -I DOCKER-USER -i eth0 ! -s 10.0.0.0/24 -p tcp \
-m conntrack --ctorigdstport 8080 -j DROP
答案3
好的,如果您希望您的 redis 只能从特定主机访问,您需要使用 iptables。
iptables -I FORWARD -i public_iface -p tcp --dport 6379 -j DROP
iptables -I FORWARD -i public_iface -s allowed_ip -p tcp --dport 6379 -j ACCEPT
由于我不知道您的 iptables 中是否已经有内容,因此我选择在顶部插入规则。
-I 选项将规则插入到链的顶部,因此首先插入规则以拒绝所有 redis 的流量,然后在该规则之前插入另一条规则以允许来自特定主机的流量。
这里我们使用FORWARD链而不是INPUT链,因为流量不是直接发往docker主机。
您可以使用以下方式检查结果
iptables -L
您可能想了解如何使防火墙规则能够防重启。
答案4
只需删除 redis 容器中的“端口”部分,它的唯一功能就是将其暴露给世界。由于您将 2 个容器配置为位于后端网络中,因此它们无论如何都能够进行通信