与 Docker 默认的网络模型不同,Kubernetes 形成了一套自己的网络模型,该网络模型更加适应传统的网络模式,应用能够平滑的从非容器环境迁移到 Kubernetes 环境中。
自从 Docker 容器出现,容器的网络通信一直是众人关注的焦点,而容器的网络方案又可以分为两大部分:
利用 Net Namespace 可以为 Docker 容器创建隔离的网络环境,容器具有完全独立的网络栈,与宿主机隔离。也可以使 Docker 容器共享主机或者其他容器的网络命名空间。
我们在使用docker run
创建 Docker 容器时,可以使用--network=
选项指定容器的网络模式,Docker 有以下 4 种网络模式:
--network=host
指定,不支持多主机;--network=bridge
指定,默认设置,不支持多主机;--network=container:NAME_or_ID
指定,即joiner 容器
,不支持多主机;--network=none
指定,不支持多主机。连接到 host 网络的容器共享 Docker host 的网络栈,容器的网络配置与 host 完全一样。
我们先查看一下主机的网络。
[root@datanode03 ~]# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:44:8d:48:70 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
enp1s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.203 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::2e0:70ff:fe92:4779 prefixlen 64 scopeid 0x20<link>
ether 00:e0:70:92:47:79 txqueuelen 1000 (Ethernet)
RX packets 46093 bytes 66816291 (63.7 MiB)
RX errors 0 dropped 1 overruns 0 frame 0
TX packets 24071 bytes 1814769 (1.7 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 0 (Local Loopback)
RX packets 170 bytes 107720 (105.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 170 bytes 107720 (105.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
然后创建 host 网络的容器,再查看容器的网络信息。
[root@datanode03 ~]# docker run -it --network=host busybox
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
90e01955edcd: Pull complete
Digest: sha256:2a03a6059f21e150ae84b0973863609494aad70f0a80eaeb64bddd8d92465812
Status: Downloaded newer image for busybox:latest
/ # ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:44:8D:48:70
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
enp1s0 Link encap:Ethernet HWaddr 00:E0:70:92:47:79
inet addr:192.168.1.203 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::2e0:70ff:fe92:4779/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:45850 errors:0 dropped:1 overruns:0 frame:0
TX packets:23921 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:66794758 (63.7 MiB) TX bytes:1783655 (1.7 MiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:170 errors:0 dropped:0 overruns:0 frame:0
TX packets:170 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:107720 (105.1 KiB) TX bytes:107720 (105.1 KiB)
在容器中可以看到 host 的所有网卡,并且连 hostname 也是 host 的,可以直接使用宿主机 IP 地址与外界通信,无需额外进行 NAT 转换。由于容器通信时,不再需要通过 Linux Bridge 等方式转发或者数据包的封装,性能上有很大的优势。
当然,Host 模式有利也有弊,主要包括以下缺点:
Bridge 模式是 Docker 默认的网络模式,也是开发者最常用的网络模式。在这种模式下,Docker 为容器创建独立的网络栈,保证容器内的进行使用独立的网络环境,实现容器之间,容器与宿主机之间的网络栈隔离。同时,通过宿主机上的 Docker0 网桥,容器可以与宿主机乃至外界进行网络通信。
从上图可以看出,容器是可以与宿主机以及外界的其他机器通信的,同一宿主机上,容器之间都是桥接在 Docker0 这个网桥上,Docker0 作为虚拟交换机使容器间互相通信。但是,由于宿主机的 IP 地址与容器 veth pair 的 IP 地址均不在同一个网段,故仅仅依靠 veth pair 和 NameSpace 的技术并不足以使宿主机以外的网络主动发现容器的存在。Docker 采用了端口绑定的方式(通过 iptables 的 NAT),将宿主机上的端口流量转发到容器内的端口上,这样一来,外界就可以与容器中的进程进行通信。
Container 模式是一种特殊的网络模式。该模式下的容器使用其他容器的网络命名空间,网络隔离性会处于 Bridge 模式与 Host 模式之间。也就是说,当容器与其他容器共享网络命名空间时,这两个容器间不存在网络隔离,但他们与宿主机机器其他容器又存在网络隔离。
Container 模式的容器可以通过 localhost 来与同一网络命名空间下的其他容器通信,传输效率高。这种模式节约了一定数量的网络资源,但并没有改变容器与外界的通信方式。在 Kubernetes 体系架构下引入 Pod 概念,Kubernetes 为 Pod 创建一个基础设施容器,同一 Pod 下的其他容器都以 Container 模式共享这个基础设施容器的网络命名空间,相互之间以 localhost 访问,构成一个统一的整体。
与前几种不同,None 模式的 Docker 容器拥有自己的 Network Namespace,但并不为 Docker 容器进行网络配置。也就是说,该 Docker 容器没有网卡、IP、路由等信息。需要用户为 Docker容器添加网卡、配置 IP 等。
常见的跨主机通信方案主要有以下几种:
主机 IP + 端口
的方式访问容器中的服务。显然,这种方式仅能支持网络栈的 4 层及以上的应用,并且容器与宿主机紧耦合,很难灵活地处理问题,可扩展性不佳;容器网络发展到现在,形成了两大阵营:
CNM 和 CNI 是网络规范或者网络体系,并不是网络实现,因此不关心容器的网络实现方式,CNM 和 CNI 关心的只是网络管理。
但从网络实现角度,又可分为:
隧道方案:隧道方案在 IaaS 层的网络中应用也比较多,它的主要缺点是随着节点规模的增长复杂度会提升,而且出了网络问题后跟踪起来比较麻烦,大规模集群情况下这是需要考虑的一个问题。
路由方案:一般是基于3层或者2层实现网络隔离和跨主机容器互通的,出了问题也很容易排查。
Calico:基于 BGP 协议的路由方案,支持很细致的 ACL 控制(Nerwork Policy),对混合云亲和度比较高。
Macvlan:从逻辑和 Kernel 层来看,是隔离性和性能最优的方案。基于二层隔离,所以需要二层路由器支持,大多数云服务商不支持,所以混合云上比较难以实现。
Kubernetes 采用的是基于扁平地址空间的网络模型,集群中的每个 Pod 都有自己的 IP 地址,Pod 之间不需要配置 NAT 就能直接通信。另外,同一个 Pod 中的容器共享 Pod 的 IP,能够通过 localhost 通信。
这种网络模型对应用开发者和管理员相当友好,应用可以非常方便地从传统网络迁移到 Kubernetes。每个 Pod 可被看作是一个个独立的系统,而 Pod 中的容器则可被看做同一系统中的不同进程。
Pod 内容器之间的通信
当 Pod 被调度到某个节点,Pod 中的所有容器都在这个节点上运行,这些容器共享相同的本地文件系统、IPC 和网络命名空间。
不同 Pod 之间不存在端口冲突的问题,因为每个 Pod 都有自己的 IP 地址。当某个容器使用 localhost 时,意味着使用的是容器所属 Pod 的地址空间。
比如 Pod A 有两个容器 container-A1 和 container-A2,container-A1 在端口 1234 上监听,当 container-A2 连接到 localhost:1234,实际上就是在访问 container-A1。这不会与同一个节点上的 Pod B 冲突,即使 Pod B 中的容器 container-B1 也在监听 1234 端口。
Pod 之间的通信
Pod 的 IP 是集群可见的,即集群中的任何其他 Pod 和节点都可以通过 IP 直接与 Pod 通信,这种通信不需要借助任何的网络地址转换、隧道或代理技术。Pod 内部和外部使用的是同一个 IP,这也意味着标准的命名服务和发现机制,比如 DNS 可以直接使用。
Pod 与 Service 的通信
Pod 间可以直接通过 IP 地址通信,但前提是 Pod 得知道对方的 IP。在 Kubernetes 集群中, Pod 可能会频繁的销毁和创建,也就是说 Pod 的 IP 不是固定的。为了解决这个问题,Service 提供了访问 Pod 的抽象层。无论后端的 Pod 如何变化,Service 都作为稳定的前端对外提供服务。同时,Service 还提供了高可用和负载均衡功能,Service 负责将请求转发给正确的 Pod。
外部访问
无论是 Pod 的 IP 还是 Service 的 Cluster IP,它们只能在 Kubernetes 集群中可见,对集群之外的世界,这些 IP 都是私有的。
Kubernetes 安装方式。
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/bc79dd1505b0c8681ece4de4c0d86c5cd2643275/Documentation/kube-flannel.yml
flannel 是 CoreOS 开发的容器网络解决方案。flannel 为每个 host 分配一个 subnet,容器从此 subnet 中分配 IP,这些 IP 可以在 host 间路由,容器间无需 NAT 和 port mapping 就可以跨主机通信。
每个 subnet 都是从一个更大的 IP 池中划分的,flannel 会在每个主机上运行一个叫 flanneld 的 agent,其职责就是从池子中分配 subnet。为了在各个主机间共享信息,flannel 用 etcd(与 consul 类似的 key-value 分布式数据库)存放网络配置、已分配的 subnet、host 的 IP 等信息。
数据包如何在主机间转发是由 backend 实现的。flannel 提供了多种 backend,有 UDP、vxlan、host-gw、aws-vpc、gce 和 alloc 路由,最常用的有 vxlan 和 host-gw。
Flannel 实质上是一种叠加网络(Overlay Network),也就是将 TCP 数据包装在另一种网络包里面进行路由转发和通信。
我们使用 kubectl apply 安装的 flannel 默认的 backend 为 vxlan,host-gw 是 flannel 的另一个 backend,我们将前面的 vxlan backend 切换成 host-gw。
与 vxlan 不同,host-gw 不会封装数据包,而是在主机的路由表中创建到其他主机 subnet 的路由条目,从而实现容器跨主机通信。要使用 host-gw 首先修改 flannel 的配置 flannel-config.json
:
kubectl edit cm kube-flannel-cfg -o yaml -n kube-system
找到如下字段进行修改。
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
host-gw 把每个主机都配置成网关,主机知道其他主机的 subnet 和转发地址,由于 vxlan 需要对数据包进行额外的打包和拆包,性能会比 vxlan 强一些。
Kubernetes 安装方式。
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
Calico 把每个操作系统的协议栈当做一个路由器,然后认为所有的容器是连在这个路由器上的网络终端,在路由器之间运行标准的路由协议——BGP,然后让他们自己去学习这个网络拓扑该如何转发,所以Calico 是一个纯三层的虚拟网络方案,Calico 为每个容器分配一个 IP,每个 host 都是 router,把不同 host 的容器连接起来。与 VxLAN 不同的是,Calico 不对数据包做额外封装,不需要 NAT 和端口映射,扩展性和性能都很好。
与其他容器网络方案相比,Calico 还有一大优势:network policy。用户可以动态定义 ACL 规则,控制进出容器的数据包,实现业务需求。
Network Policy
Network Policy 是 Kubernetes 的一种资源。Network Policy 通过 Label 选择 Pod,并指定其他 Pod 或外界如何与这些 Pod 通信。
默认情况下,所有 Pod 是非隔离的,即任何来源的网络流量都能够访问 Pod,没有任何限制。当为 Pod 定义了 Network Policy,只有 Policy 允许的流量才能访问 Pod。
不过,不是所有的 Kubernetes 网络方案都支持 Network Policy。比如 Flannel 就不支持,Calico 是支持的。我们接下来将用 Canal 来演示 Network Policy。Canal 这个开源项目很有意思,它用 Flannel 实现 Kubernetes 集群网络,同时又用 Calico 实现 Network Policy。
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/canal/rbac.yaml
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/canal/canal.yaml
请查看我的博文 https://blog.51cto.com/wzlinux/2112061 。
docker network create -d macvlan --subnet=172.16.86.0/24 --gateway=172.16.86.1 -o parent=enp0s9 mac_net1
macvlan 本身是 linxu kernel 模块,其功能是允许在同一个物理网卡上配置多个 MAC 地址,即多个 interface,每个 interface 可以配置自己的 IP。macvlan 本质上是一种网卡虚拟化技术,Docker 用 macvlan 实现容器网络就不奇怪了。
macvlan 的最大优点是性能极好,相比其他实现,macvlan 不需要创建 Linux bridge,而是直接通过以太 interface 连接到物理网络。
macvlan 会独占主机的网卡,也就是说一个网卡只能创建一个 macvlan 网络:
docker network create -d macvlan -o parent=enp0s9 mac_net2
好在 macvlan 不仅可以连接到 interface(如 enp0s9),也可以连接到 sub-interface(如 enp0s9.xxx)。
VLAN 是现代网络常用的网络虚拟化技术,它可以将物理的二层网络划分成多达 4094 个逻辑网络,这些逻辑网络在二层上是隔离的,每个逻辑网络(即 VLAN)由 VLAN ID 区分,VLAN ID 的取值为 1-4094。
Linux 的网卡也能支持 VLAN(apt-get install vlan),同一个 interface 可以收发多个 VLAN 的数据包,不过前提是要创建 VLAN 的 sub-interface。
比如希望 enp0s9 同时支持 VLAN10 和 VLAN20,则需创建 sub-interface enp0s9.10 和 enp0s9.20。
在交换机上,如果某个 port 只能收发单个 VLAN 的数据,该 port 为 Access 模式,如果支持多 VLAN,则为 Trunk 模式,所以接下来实验的前提是:
enp0s9 要接在交换机的 trunk 口上。不过我们用的是 VirtualBox 虚拟机,则不需要额外配置了。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。