docker-compose 仅​​向外界公开一个网络

docker-compose 仅​​向外界公开一个网络

我有一个通过 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 个网络,frontendbackend。问题很简单:

  • 我如何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 个容器配置为位于后端网络中,因此它们无论如何都能够进行通信

相关内容