这最初发布在这里:https://aly.arriqaaq.com/linux-networking-bridge-iptables-and-docker/

这一系列文章是我学习容器编排平台(Docker、Kubernetes等)相关的各种网络概念的日志

[](https://res.cloudinary.com/practicaldev/image/fetch/s--5oo8Fl7O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1. medium.com/max/2560/1%2Av5c5nl2BoA0BqwqWoj5Y1w.jpeg)

Linux Networking 是一个非常有趣的话题。在本系列中,我的目标是深入了解这些容器编排平台在底层实现网络内部的各种方式。

入门

在开始之前有几个问题。

1) 什么是命名空间?

TLDR,linux 命名空间是对操作系统中资源的抽象。命名空间就像独立的房子,拥有自己的独立资源集。目前有 7 种命名空间类型 Cgroup、IPC、Network、Mount、PID、User、UTS

网络隔离是我们感兴趣的,因此我们将深入讨论网络命名空间。

2) 如何跟进?

本文中的所有示例都是在全新的 vagrantUbuntu Bionic虚拟机上制作的。

Linux Networking 是一个非常有趣的话题。在本系列中,我的目标是深入了解这些容器编排平台在底层实现网络内部的各种方式。

入门

在开始之前有几个问题。

1) 什么是命名空间?

TLDR,linux 命名空间是对操作系统中资源的抽象。命名空间就像独立的房子,拥有自己的独立资源集。目前有 7 种命名空间类型 Cgroup、IPC、Network、Mount、PID、User、UTS

网络隔离是我们感兴趣的,因此我们将深入讨论网络命名空间。

2) 如何跟进?

本文中的所有示例都是在全新的 vagrantUbuntu Bionic虚拟机上制作的。

Linux Networking 是一个非常有趣的话题。在本系列中,我的目标是深入了解这些容器编排平台在底层实现网络内部的各种方式。

入门

在开始之前有几个问题。

1) 什么是命名空间?

TLDR,linux 命名空间是对操作系统中资源的抽象。命名空间就像独立的房子,拥有自己的独立资源集。目前有 7 种命名空间类型 Cgroup、IPC、Network、Mount、PID、User、UTS

网络隔离是我们感兴趣的,因此我们将深入讨论网络命名空间。

2) 如何跟进?

本文中的所有示例都是在全新的 vagrantUbuntu Bionic虚拟机上制作的。

vagrant init ubuntu/bionic64
vagrant up
vagrant ssh

进入全屏模式 退出全屏模式

探索网络命名空间

平台如何通过为容器分配专用的网络堆栈来虚拟化网络资源以隔离容器,并确保这些容器不会干扰主机(或相邻容器)?网络命名空间。网络命名空间隔离网络相关资源——在不同的网络命名空间中运行的进程有自己的网络设备、路由表、防火墙规则等。让我们快速创建一个。

ip netns add ns1

进入全屏模式 退出全屏模式

瞧!您已经像这样创建了独立的网络命名空间 (ns1)。现在您可以继续运行此命名空间内的任何进程。

ip netns exec ns1 python3 -m http.server 8000

进入全屏模式 退出全屏模式

这很整洁! exec $namespace $command 在命名的网络命名空间 $namespace 中执行 $command。这意味着进程在自己的网络堆栈中运行,与主机分离,并且只能通过网络命名空间中定义的接口进行通信。

主机命名空间在您继续阅读之前,我想提请您注意 **host **network 的默认命名空间。让我们列出所有的命名空间

ip netns

# all namespaces
ns1
default

进入全屏模式 退出全屏模式

您可以注意到创建的 default ** 命名空间。这是 **host 命名空间,这意味着您仅在 VM 或机器上运行的任何服务都在此命名空间下运行。向前推进这一点很重要。

创建网络命名空间

话虽如此,让我们快速前进并创建两个隔离的网络命名空间(类似于两个容器)

#!/usr/bin/env bash

NS1="ns1"
NS2="ns2"

# create namespace
ip netns add $NS1
ip netns add $NS2

进入全屏模式 退出全屏模式

连接电缆

我们需要继续将这些命名空间连接到我们的主机网络。 vETH(虚拟以太网)设备有助于建立这种连接。 vETH 是本地以太网隧道,设备是成对创建的。在一对设备上传输的数据包会立即在另一台设备上接收。当任一设备关闭时,该对的链路状态为关闭。

#!/usr/bin/env bash

NS1="ns1"
VETH1="veth1"
VPEER1="vpeer1"

NS2="ns2"
VETH2="veth2"
VPEER2="vpeer2"

# create namespace
ip netns add $NS1
ip netns add $NS2

# create veth link
ip link add ${VETH1} type veth peer name ${VPEER1}
ip link add ${VETH2} type veth peer name ${VPEER2}

进入全屏模式 退出全屏模式

把 VETH 想象成一根网线。一端连接到主机网络,另一端连接到创建的网络命名空间。让我们继续连接电缆,然后启动这些接口。

# setup veth link
ip link set ${VETH1} up
ip link set ${VETH2} up

# add peers to ns
ip link set ${VPEER1} netns ${NS1}
ip link set ${VPEER2} netns ${NS2}

进入全屏模式 退出全屏模式

本地主机

有没有想过 localhost 是如何工作的?嗯,环回接口将流量引导到本地系统内。因此,当您在 localhost (127.0.0.1) 上运行某些东西时,您实际上是在使用环回接口来路由流量。如果我们想在本地运行服务,让我们启动环回接口,并在我们的网络命名空间中启动对等接口以开始接受流量。

# setup loopback interface
ip netns exec ${NS1} ip link set lo up
ip netns exec ${NS2} ip link set lo up

# setup peer ns interface
ip netns exec ${NS1} ip link set ${VPEER1} up
ip netns exec ${NS2} ip link set ${VPEER2} up

进入全屏模式 退出全屏模式

为了连接到网络,一台计算机必须至少有一个网络接口。每个网络接口都必须有自己唯一的 IP 地址。你给主机的 IP 地址被分配给它的网络接口。但是每个网络接口都需要一个 IP 地址吗?嗯,不是真的。我们将在接下来的步骤中看到这一点。

# assign ip address to ns interfaces
VPEER_ADDR1="10.10.0.10"
VPEER_ADDR2="10.10.0.20"

ip netns exec ${NS1} ip addr add ${VPEER_ADDR1}/16 dev ${VPEER1}
ip netns exec ${NS2} ip addr add ${VPEER_ADDR2}/16 dev ${VPEER2}

进入全屏模式 退出全屏模式

请记住,这里我们只为网络命名空间内的接口分配了网络地址(ns1 (vpeer1), ns2 (vpeer2))。主机命名空间接口没有分配 IP (veth1, veth2)。为什么?我们需要它吗?嗯,不是真的。

建造桥梁,而不是墙壁

男人建的墙太多,桥不够

请记住,当您运行多个容器并希望将流量发送到这些容器时,我们需要一个桥接器来连接它们。 网桥从多个通信网络或网段创建单个聚合网络。 网桥是一种以独立于协议的方式将两个以太网段连接在一起的方式。 **数据包是基于以太网地址*转发的,而不是IP地址(如路由器)。 *由于转发是在第 2 层完成的,所有协议都可以透明地通过网桥。桥接不同于路由。路由允许多个网络独立通信但保持独立,而桥接则连接两个独立的网络,就好像它们是一个网络一样。

Docker 在下面有一个 **docker0 **bridge 来引导流量。当 Docker 服务启动时,会在主机上创建一个 Linux 网桥。容器上的各种接口与网桥通信,网桥代理外部世界。同一主机上的多个容器可以通过 Linux 网桥相互通信。

因此,让我们继续创建一座桥梁。

BR_ADDR="10.10.0.1"
BR_DEV="br0"

# setup bridge
ip link add ${BR_DEV} type bridge
ip link set ${BR_DEV} up

# assign veth pairs to bridge
ip link set ${VETH1} master ${BR_DEV}
ip link set ${VETH2} master ${BR_DEV}

# setup bridge ip
ip addr add ${BR_ADDR}/16 dev ${BR_DEV}

进入全屏模式 退出全屏模式

现在我们已经有了连接到网桥的网络接口,这些接口如何知道如何将流量引导到主机?两个网络命名空间中的路由表仅具有各自子网 IP 范围的路由条目。

由于我们将 VETH 对连接到网桥,因此网桥网络地址可用于这些网络命名空间。让我们添加一个默认路由来将流量引导到网桥。

# add default routes for ns
ip netns exec ${NS1} ip route add default via ${BR_ADDR}
ip netns exec ${NS2} ip route add default via ${BR_ADDR}

进入全屏模式 退出全屏模式

完毕。甜的!我们有一个适当的设置来测试我们的容器是否可以相互通信。最后让我们与命名空间进行交互。

# add default routes for ns
ip netns exec ${NS1} ping ${VPEER_ADDR2}

PING 10.10.0.20 (10.10.0.20) 56(84) bytes of data.
64 bytes from 10.10.0.20: icmp_seq=1 ttl=64 time=0.045 ms
64 bytes from 10.10.0.20: icmp_seq=2 ttl=64 time=0.039 ms

ip netns exec ${NS2} ping ${VPEER_ADDR1}
PING 10.10.0.10 (10.10.0.10) 56(84) bytes of data.
64 bytes from 10.10.0.10: icmp_seq=1 ttl=64 time=0.045 ms
64 bytes from 10.10.0.10: icmp_seq=2 ttl=64 time=0.039 ms

进入全屏模式 退出全屏模式

化妆舞会

我们能够在命名空间之间发送流量,但我们还没有测试在容器外发送流量。为此,我们需要使用 IPTables 来伪装来自我们命名空间的传出流量。

# enable ip forwarding
bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'

iptables -t nat -A POSTROUTING -s ${BR_ADDR}/16 ! -o ${BR_DEV} -j MASQUERADE

进入全屏模式 退出全屏模式

MASQUERADE 修改数据包的源地址,将其替换为指定网络接口的地址。这类似于 SNAT,只是它不需要事先知道机器的 IP 地址。基本上,我们在这里所做的是向 NAT 表添加一个条目,以伪装从网桥传出的流量,除了对于桥梁交通本身。至此,我们完成了关于 docker 如何实际实现 linux 网络堆栈来隔离容器的基本设置。您可以在此处找到整个脚本。

现在让我们深入了解 docker 如何与各种网络设置一起工作。

Docker是如何工作的?

每个 Docker 容器都有自己的网络堆栈,其中为每个容器创建一个新的网络命名空间,与其他容器隔离。当 Docker 容器启动时,Docker 引擎会为其分配一个带有 IP 地址、默认网关和其他组件(例如路由表和 DNS 服务)的网络接口。

Docker 提供了五种网络类型。所有这些网络类型都是通过 docker0 通过 --net 标志配置的

1\。主机网络 (--netu003dhost*)*:容器共享默认主机的相同网络命名空间。

您可以轻松地验证这一点。

# check the network interfaces on the host
ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:98:b0:9b:6c:78 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 85994sec preferred_lft 85994sec
    inet6 fe80::98:b0ff:fe9b:6c78/64 scope link
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:e5:72:10:c0 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

进入全屏模式 退出全屏模式

在主机模式下运行 docker,你会看到它列出了相同的接口集。

# check the network interfaces in the container
docker run --net=host -it --rm alpine ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:98:b0:9b:6c:78 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 85994sec preferred_lft 85994sec
    inet6 fe80::98:b0ff:fe9b:6c78/64 scope link
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:e5:72:10:c0 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

进入全屏模式 退出全屏模式

2\。 Bridge Networking ( — netu003dbridge/default*):* 在这种模式下,默认网桥作为容器相互连接的桥梁。容器运行在一个隔离的网络命名空间中。对同一网络中的其他容器开放通信。与主机外部服务的通信在退出主机之前要经过网络地址转换 (NAT)。我们已经在上面看到了桥接网络的创建。

# check the network interfaces in the container
docker run --net=bridge -it --rm alpine ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
16: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

进入全屏模式 退出全屏模式

您可以注意到已经为此容器创建了一个 eth0 veth 对,并且相应的对应该存在于主机上

# check the network interfaces on the host
ip addr

21: veth8a812a3@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether d2:c4:4e:d4:08:ad brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::d0c4:4eff:fed4:8ad/64 scope link
       valid_lft forever preferred_lft forever

进入全屏模式 退出全屏模式

3\。自定义桥接网络 ( — networku003dxxx*):* 这与桥接网络相同,但使用为容器显式创建的自定义桥接。

# create custom bridge
docker network create foo
2b25342b1d883dd134ed8a36e3371ef9c3ec77cdb9e24a0365165232e31b17b6

# check the bridge interface on the host
22: br-2b25342b1d88: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:49:79:07:30 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-2b25342b1d88
       valid_lft forever preferred_lft forever
    inet6 fe80::42:49ff:fe79:730/64 scope link
       valid_lft forever preferred_lft forever

进入全屏模式 退出全屏模式

您可以看到在自定义创建桥接器时,将桥接接口添加到主机。现在,自定义网桥中的所有容器都可以与该网桥上其他容器的端口进行通信。这提供了更好的隔离和安全性。

现在让我们在不同的终端运行两个容器

# terminal 1
docker run -it --rm --name=container1 --network=foo alpine sh

# terminal 2
docker run -it --rm --name=container2 --network=foo alpine sh

# check the network interfaces on the host
ip addr

22: br-2b25342b1d88: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:49:79:07:30 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-2b25342b1d88
       valid_lft forever preferred_lft forever
    inet6 fe80::42:49ff:fe79:730/64 scope link
       valid_lft forever preferred_lft forever
30: veth86ca323@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-2b25342b1d88 state UP group default
    link/ether 1e:5e:66:ea:47:1e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::1c5e:66ff:feea:471e/64 scope link
       valid_lft forever preferred_lft forever
32: vethdf5e755@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-2b25342b1d88 state UP group default
    link/ether ba:2b:25:23:a3:40 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::b82b:25ff:fe23:a340/64 scope link
       valid_lft forever preferred_lft forever

进入全屏模式 退出全屏模式

正如预期的那样,通过桥接网络,两个容器(container1、container2)都连接了各自的 veth(veth86ca323、vethdf5e755)电缆。您只需运行以下命令即可验证此网桥:

# you can notice both the containers are connected via the same bridge
brctl show br-2b25342b1d88

bridge name bridge id       STP enabled interfaces
br-2b25342b1d88     8000.024249790730   no  veth86ca323
                            vethdf5e755

进入全屏模式 退出全屏模式

4\。 Container-defined Networking( — netu003dcontainer:$container2*):* 启用此功能后,创建的容器将与名为 $container2 的容器共享其网络命名空间。

# create a container in terminal 1
docker run -it --rm --name=container1  alpine sh

# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
33: eth0@if34: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

# create a container in terminal 1
docker run -it --rm --name=container2 --network=container:container1 alpine ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
33: eth0@if34: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

进入全屏模式 退出全屏模式

您可以看到两个容器共享相同的网络接口。

5\。无网络: 此选项禁用容器的所有网络

docker run --net=none alpine ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

进入全屏模式 退出全屏模式

您可以注意到仅启用了 lo(环回)接口,此容器中未配置任何其他内容。

在下一篇文章中,我们将深入 (inshaAllah) 了解 docker 如何操纵 iptables 规则以提供网络隔离。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐