简介
通过 Docker 容器可以实现文件系统, 网络和内核的隔离。 Docker 网络是使用 Docker 的一个很重要的知识点。 在不了解 Docker 网络的情况下使用 Docker 部署应用可能会出现 Docker 容器跨过宿主机防火墙(iptables)的限制直接与取得外网访问权的情况。 在这篇文章中将会分析安装 Docker 对宿主机网络设备和 iptables 两个重要的网络环境的影响。 并会分析何为 Docker 容器网络的隔离性, 如何通过控制宿主机 iptables 来控制 Docker 容器的网络访问权限。
跨越宿主机防火墙示例
首先在宿主机上设置一个防火墙, 将 INPUT 链的默认策略设为 DROP
远程连接万万不能使用这个命令,会导致连不上服务器的
$ iptables -P INPUT DROP
# 然后执行查看 iptables 的命令
$ iptables-save
可以看到 filter 表的 INPUT 链的默认策略为 DROP
然后 ping 百度, 由于 INPUT 链的默认规则为 DROP , 导致 DNS 服务器返回的解析结果不能正确接受, 所以这里直接 ping 解析出的 ip 地址。
$ ping -c4 14.215.177.39
执行结果为
可以看到宿主机在不使用 iptables 手动设置 INPUT 链的情况下已经和外界网络隔绝了。
接着创建一个 Docker 容器并执行 ping -c4 www.baidu.com 的命令
能够正确接收 DNS 服务器返回的解析结果, 并且能够成功 ping 通。
这就是所谓的 Docker 容器网络环境和宿主机网络环境互相隔离。 接下来就会详解 Docker 是如何操作宿主机的网络设备来实现 Docker 容器和宿主机网络隔离的。
安装 Docker前后 对宿主机网络环境的影响
安装前网络环境
首先查看安装 Docker 前的网络设备, 输入查看网络设备的命令
$ ip a
只有两个网络设备, 一个是主机的回环设备, 一个是主机的外接网卡。
接着查看安装 Docker 前的 iptables, 输入查看 iptables 的命令
$ iptables-save
然后输出的内容可能为空, 也可能是下面的内容
# Generated by iptables-save v1.6.0 on Sat Mar 16 00:29:12 2019
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
# Completed on Sat Mar 16 00:29:12 2019
无论是空还是上述内容, 都是代表着 iptables 没有经过修改。
安装后网络环境
这里使用的 Docker 版本是 Docker version 18.09.3
可以看到安装后, 网络设备中增加了 docker0 设备。 这个新增网络设备的作用将会在后面说到。
接着还是使用 iptables-save 命令查看 iptables。 显示如下
# Generated by iptables-save v1.6.0 on Sat Mar 16 00:41:26 2019
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [4:237]
:POSTROUTING ACCEPT [4:237]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
COMMIT
# Completed on Sat Mar 16 00:41:26 2019
# Generated by iptables-save v1.6.0 on Sat Mar 16 00:41:26 2019
*filter
:INPUT ACCEPT [7:704]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [8:669]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Sat Mar 16 00:41:26 2019
可以看到安装 Docker 对 iptables 的 NAT 表和 FILTER 表都作了较大的改动.
NAT 表改动的解析如下:
# DOCKER 链
:DOCKER - [0:0]
# 如果请求的目标地址是本机的地址, 那么将请求转到 DOCKER 链处理
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
# 如果请求的目标地址不匹配 127.0.0.0/8, 并且目标地址属于本机地址, 那么将请求跳转到 DOCKER 链处理
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
# 对于来自于 172.17.0.0/16 的请求, 目标地址不是 docker0 所在的网段的地址, POSTROUTING 链将会将该请求伪装成宿主机的请求转发到外网
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
# 由 docker0 设备传入的请求 DOCKER 链会返回上一层处理
-A DOCKER -i docker0 -j RETURN
FILTER 表改动的解析如下:
# DOCKER 链
:DOCKER - [0:0]
# DOCKER-ISOLATION-STAGE-1 链
:DOCKER-ISOLATION-STAGE-1 - [0:0]
# DOCKER-ISOLATION-STAGE-2 链
:DOCKER-ISOLATION-STAGE-2 - [0:0]
# DOCKER-USER 链
:DOCKER-USER - [0:0]
# FORWARD 链的请求跳转到 DOCKER-USER 链处理
-A FORWARD -j DOCKER-USER
# FORWARD 链的请求跳转到 DOCKER-ISOLATION-STAGE-1 链处理
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
# FORWARD 链的请求如果目标是 docker0 所在的网段, 而且已经建立的连接或者和已建立连接相关那么接受请求
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# FORWARD 链请求目标是 docker0 所在的网段, 那么跳转到 DOCKER 链处理
-A FORWARD -o docker0 -j DOCKER
# FORWARD 链的请求来自于 docker0 所在网段, 而且目标网段不是 docker0 所在网段, 那么接收请求.
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
# FORWARD 链的请求来自于 docker0 所在网段, 而且目标网段也是 docker0 所在网段, 那么接收请求
-A FORWARD -i docker0 -o docker0 -j ACCEPT
# DOCKER-ISOLATION-STAGE-1 链的请求如果来自 docker0 所在网段, 而且目标网段不属于 docker0 所在网段, 那么跳转到 DOCKER-ISOLATION-STAGE-2 处理
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
# DOCKER-ISOLATION-STAGE-1 链未处理的请求返回到上一层继续处理
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
# DOCKER-ISOLATION-STAGE-2 链的请求如果目标的网段为 docker0 所在网段则丢弃请求
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
# DOCKER-ISOLATION-STAGE-2 链未处理的请求返回到上一层继续处理
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
# DOCKER-USER 链未处理的请求返回到上一层继续处理
-A DOCKER-USER -j RETURN
一般情况下 iptables 处理请求的流程如下
安装 Docker 后 Docker 添加了 DOCKER, DOCKER-USER, DOCKER-ISOLATION-STAGE-1, DOCKER-ISOLATION-STAGE-2 四条链, 按照初始化创建的 iptables 可以得出现在处理请求的流程如下
iptables 的处理流程明显变复杂了。 主要是 PREROUTING 链, FORWARD 链和 OUTPUT 链后都追加了 DOCKER 链。
详析 Docker 如何跨越主机 iptables
容器的请求通过 docker0 网卡进入到宿主机中, 按照上面的 iptables 的处理流程图解析容器 ping www.baidu.com 的过程。
由于 DOCKER-USER, DOCKER-ISOLATION-STAGE-1, DOCKER-ISOLATION-STAGE-2没有自定义规则所以这里解析一般会跳过, 这几条链
- 请求进入 PREROUTING 链
- www.baidu.com 的目标 ip 地址不属于宿主机的 ip 地址, 进入 FORWARD 链处理
- FORWARD 链的默认规则为抛弃, 但是根据规则
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
由 docker0 接收的请求允许通过, 进入到 POSTROUTING 链。 - 根据 POSTROUTING 链的规则
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
, 对源地址符合 172.17.0.0/16 模式的请求伪装成宿主机的请求发往外网。 - 外网响应通过宿主机网卡进入到 PREROUTING 链处理, 目标地址为 172.17.0.2/16。
- 172.17.0.2/16 不属于宿主机的 ip 地址, 进入 FORWARD 链处理
- 172.17.0.2/16 的网关是 docker0, 所以目标输出是 docker0, 根据 FORWARD 链的规则
-A FORWARD -o docker0 -j DOCKER
, 输出到 docker0 的请求会进入到 DOCKER 链处理。 - DOCKER 链自定义规则为空, 返回到 FORWARD 继续处理, 最终进入到 POSTROUTING。 POSTROUTING 处理后最终从 docker0 输出进入到容器内部。
很明显, 容器的请求没有经过宿主机的 INPUT 链, 所以在宿主机的 INPUT 链上做规则是没法限制容器的网络访问的。 需要限制容器网络访问应该对 DOCKER 链动手脚。
接下来我们对这个结论进行验证:
输入下面命令将 DOCKER 链对源地址为 14.215.177.38(百度的服务器) 的数据包的策略改为 DROP
$ iptables -A DOCKER -s 14.215.177.38 -j DROP
接着进入容器ping www.baidu.com,发现依然可以ping通, 也就是说 FORWARD 链上的请求没有转移到 DOCKER 链就已经被接受了。 观察 iptables 的规则, 应该是 -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
这条规则导致请求在进入 DOCKER 链之前就已经被接受。 这个规则是很模糊的, 如果数据包来自于已经建立连接的双方或者和已建立的连接有关则接受。 为了模拟这条规则的情况同时要让 DOCKER 链的规则生效, 可以删除这条规则, 在 DOCKER 链的末尾增加通过所有请求的规则, 结合之前添加了的阻止来自 14.215.177.38 的数据包, 可以达到只丢弃来自 14.215.177.38 的数据包的目标, 依次执行命令
$ iptables -D FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
$ iptables -A DOCKER -j ACCEPT
这次成功,这个结果可以印证, 除了源于 14.215.177.38 的数据包外, 其他的数据包都被接受了。 原因有三
- mirrors.163.com 域名能够正确解析, 说明和 DNS 服务器之间的通信没有被阻止
- 可以在 mirrors.163.com 中正确安装 inetutils-ping 说明和 mirrors.163.com 之间的通信没有被阻止
- 不能正确向 14.215.177.38 发出 ping 操作, 说明 14.215.177.38 的通信被阻止了
另一种跨越主机iptables的方式
从上图可以看出,docker链主要是利用forward链来进行一些操作,所以有些时候有些限制就可以直接在forward链来进行限制或开放。
总结
通过上面的分析, 可以得出, 所谓的 Docker 网络的隔离性只在 INPUT 链, OUTPUT 链中体现。 修改 PREROUTING 链, FORWARD 链, POSTROUTING 链都会影响到 Docker 容器的网络环境。 要通过 DOCKER 链控制 Docker 容器的访问权限, 需要先删除 -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
这条规则, 因为这条规则的不确定性太大。 然后单纯通过 DOCKER 链来控制 Docker 容器的网络访问权限。
转载自:
https://blog.csdn.net/qq_17004327/article/details/88630194