手工模拟实现 Docker 容器网络!

行家益,吾是飞哥!

现在服务器虚拟化技术已经发展到了深水区。现在业界已经有许众公司都迁移到容器上了。吾们的开发写出来的代码也许率是要运走在容器上的。因此深切理解容器网络的做事原理专门的主要。这有云云异日遇到题目的时候才晓畅该如何动手处理。

网络虚拟化,其实用一句话来概括就是用柔件来模拟实实际在的物理网络连接。比如 Docker 就是用纯柔件的手段在宿主机上模拟出来的自力网络环境。吾们今天来徒手打造一个虚拟网络,实现在这个网络里访问外网资源,同时监听端口挑供对外服务的功能。

望完这一篇后,坚信你对 Docker 虚拟网络能有进一步的理解。益了,吾们最先!

一、基础知识回顾 1.1 veth、bridge 与 namespace

Linux 下的 veth 是一对儿虚拟网卡设备,和吾们常见的 lo 很雷同。在这边设备里,从一端发送数据后,内核会追求该设备的另一半,于是在另外一端就能收到。不过 veth 只能解决一对一通信的题目。细目参见轻盈理解 Docker 网络虚拟化基础之 veth 设备!

倘若有许众对儿 veth 必要互一致信的话,就必要引入 bridge 这个虚拟交换机。各个 veth 对儿能够把一头连接在 bridge 的接口上,bridge 能够和交换机雷同在端口之间转发数据,使得各个端口上的 veth 都能够互一致信。参见

Namespace 解决的是阻隔性的题目。每个虚拟网卡设备、进程、socket、路由外等等网络栈有关的对象默认都是归属在 init_net 这个缺省的 namespace 中的。不过吾们期待分歧的虚拟化环境之间是阻隔的,用 Docker 来举例,那就是不及让 A 容器用到 B 容器的设备、路由外、socket 等资源,甚至连望一眼都不能够。只有云云才能保证分歧的容器之间复用资源的同时,还不会影响其它容器的平常运走。参见

经历 veth、namespace 和 bridge 吾们在一台 Linux 上就能虚拟众个网络环境出来。而且它们之间、和宿主机之间都能够互一致信。

但是这三篇文章事后,吾们还剩下一个题目异国解决,那就是虚拟出来的网络环境和外部网络的通信。还拿 Docker 容器来举例,你启动的容器里的服务一定是必要访问外部的数据库的。还有就是能够必要袒露比如 80 端口对外挑供服务。例如在 Docker 中吾们经历下面的命令将容器的 80 端口上的 web 服务要能被外网访问的到。

吾们今天的文章主要就是解决这两个题目的,一是从虚拟网络中访问外网,二是在虚拟网络中挑供服务供外网行使。解决它们必要用到路由和 nat 技术。

1.2 路由选择

Linux 是在发送数据包的时候,会涉及到路由过程。这个发送数据包既包括本机发送数据包,也包括途径现在机器的数据包的转发。

先来望本机发送数据包。其中本机发送在25 张图,一万字,拆解 Linux 网络包发送过程这一篇吾们商议过。

所谓路由其实很浅易,就是该选择哪张网卡(虚拟网卡设备也算)将数据写进去。到底该选择哪张网卡呢,规则都是在路由外中指定的。Linux 中能够有众张路由外,最主要和常用的是 local 和 main。

local 路由外中同一记录本地,实在的说是本网络命名空间中的网卡设备 IP 的路由规则。

#ip route list table local  local 10.143.x.y dev eth0 proto kernel scope host src 10.143.x.y  local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 

其它的路由规则,清淡都是在 main 路由外中记录着的。能够用 ip route list table local 查望,也能够用更简短的 route -n

再望途径现在机器的数据包的转发。除了本机发送以外,转发也会涉及路由过程。倘若 Linux 收到数据包以后发现现在标地址并不是本地的地址的话,就能够选择把这个数据包从本身的某个网卡设备上转发出去。这个时候和本机发送雷同,也必要读取路由外。按照路由外的配置来选择从哪个设备将包转走。

不过值得仔细的是,Linux 上转发功能默认是关闭的。也就是发现现在标地址不是本机 IP 地址默认是将包直接屏舍。必要做一些浅易的配置,然后 Linux 才能够干像路由器雷同的活儿,实现数据包的转发。

1.3 iptables 与 NAT

Linux 内核网络栈在运走上基本上是一个纯内核态的东西,但为了迎相符各栽各样用户层分歧的需求,内核盛开了一些口子出来供用户层来干预。其中 iptables 就是一个非往往用的干预内核走为的工具,它在内核里埋下了五个钩子入口,这就是俗称的五链。

Linux 在授与数据的时候,在 IP 层进入 ip_rcv 中处理。再实走路由判定,发现是本机的话就进入 ip_local_deliver 进走本机授与,末了送去 TCP 制定层。在这个过程中,埋了两个 HOOK,第一个是 PRE_ROUTING。这段代码会实走到 iptables 中 pre_routing 里的各栽外。发现是本地授与后接着又会实走到 LOCAL_IN,这会实走到 iptables 中配置的 input 规则。

在发送数据的时候,查找路由外找到出口设备后,挨次经历 __ip_local_out、 ip_output 等函数将包送到设备层。在这两个函数中别离过了 OUTPUT 和 PREROUTING 开的各栽规则。

倘若是转发过程,Linux 收到数据包发现不是本机的包能够经历查找本身的路由外找到正当的设备把它转发出去。那就先是在 ip_rcv 中将包送到 ip_forward 函数中处理,末了在 ip_output 函数中将包转发出去。在这个过程中别离过了 PREROUTING、FORWARD 和 POSTROUTING 三个规则。

综上所述,iptables 里的五个链在内核网络模块中的位置就能够归纳成如下这幅图。

数据授与过程走的是 1 和 2,发送过程走的是 4 、5,转发过程是 1、3、5。有了这张图,吾们能更晓畅地理解 iptable 和内核的有关。

在 iptables 中,按照实现的功能的分歧,又分成了四张外。别离是 raw、mangle、nat 和 filter。其中 nat 外实现吾们常说的 NAT(Network AddressTranslation) 功能。其中 nat 又分成 SNAT(Source NAT)和 DNAT(Destination NAT)两栽。

SNAT 解决的是内网地址访问外部网络的题目。它是经历在 POSTROUTING 里修改来源 IP 来实现的。

DNAT 解决的是内网的服务要能够被外部访问到的题目。它在经历 PREROUTING 修改现在标 IP 实现的。

二、 实现虚拟网络外网通信

基于以上的基础知识,吾们用纯手工的手段搭建一个能够和 Docker 雷同的虚拟网络。而且要实现和外网通信的功能。

1. 实验环境准备

吾们先来创建一个虚拟的网络环境出来,其命名空间为 net1。宿主机的 IP 是 10.162 的网段,能够访问外部机器。虚拟网络为其分配 192.168.0 的网段,这个网段是私有的,外部机器无法识别。

这个虚拟网络的搭建过程如下。先创建一个 netns 出来,命名为 net1。

# ip netns add net1 

创建一个 veth 对儿(veth1 - veth1_p),把其中的一头 veth1 放在 net1 中,给它配置上 IP,并把它启动首来。

# ip link add veth1 type veth peer name veth1_p  # ip link set veth1 netns net1  # ip netns exec net1 ip addr add 192.168.0.2/24 dev veth1 # IP  # ip netns exec net1 ip link set veth1 up 

创建一个 bridge,给它也竖立上 ip。接下来把 veth 的另外一端 veth1_p 插到 bridge 上面。末了把网桥和 veth1_p 都启动首来。

# brctl addbr br0  # ip addr add 192.168.0.1/24 dev br0  # ip link set dev veth1_p master br0  # ip link set veth1_p up  # ip link set br0 up 

云云吾们就在 Linux 上创建出了一个虚拟的网络。创建过程和 聊聊 Linux 上柔件实现的“交换机” - Bridge! 中雷同,只不过今天为了省事,只创建了一个网络出来,上一篇中创建出来了两个。

2. 乞求外网资源

现在倘若吾们上面的 net1 这个网络环境中想访问外网。这边的外网是指的虚拟网络宿主机外部的网络。

吾们倘若它要访问的另外一台机器 IP 是 10.153.*.* ,这个 10.153.*.* 后面两段原由是吾的内部网络,于是暗藏首来了。你在实验的过程中,用本身的 IP 代替即可。

吾们直接来访问一下试试

# ip netns exec net1 ping 10.153.*.*  connect: Network is unreachable 

挑示网络不通,这是怎么回事?用这段报错关键字在内核源码里搜索一下:

//file: arch/parisc/include/uapi/asm/errno.h #define ENETUNREACH 229 /* Network is unreachable */  //file: net/ipv4/ping.c static int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,    size_t len) {  ...  rt = ip_route_output_flow(net, &fl4, sk);  if (IS_ERR(rt)) {   err = PTR_ERR(rt);   rt = NULL;   if (err == -ENETUNREACH)    IP_INC_STATS_BH(net, IPSTATS_MIB_OUTNOROUTES);   goto out;  }  ... out:   return err;  } 

在 ip_route_output_flow 这边的返回值判定倘若是 ENETUNREACH 就退出了。这个宏定义注解上来望报错的新闻就是 “Network is unreachable”。

这个 ip_route_output_flow 主要是实走路由选路。于是吾们揣度能够是路由出题目了,望一下这个命名空间的路由外。

# ip netns exec net1 route -n Kernel IP routing table Destination     Gateway         Genmask         Flags Metric Ref    Use Iface 192.168.0.0     0.0.0.0         255.255.255.0   U     0      0        0 veth1 

怪不得,正本 net1 这个 namespace 下默认只有 192.168.0.* 这个网段的路由规则。吾们 ping 的 IP 是 10.153.*.* ,按照这个路由外里找不到出口。自然就发送战败了。

吾们来给 net 增补上默认路由规则,只要匹配不到其它规则就默认送到 veth1 上,同时指定下一条是它所连接的 bridge(192.168.0.1)。

# ip netns exec net1 route add default gw 192.168.0.1 veth1 

再 ping 一下试试。

# ip netns exec net1 ping 10.153.*.* -c 2  PING 10.153.*.* (10.153.*.*) 56(84) bytes of data.  --- 10.153.*.* ping statistics ---  2 packets transmitted, 0 received, 100% packet loss, time 999ms 

额益吧,照样不通。上面路由帮吾们把数据包从 veth 正确送到了 bridge 这个网桥上。接下来网桥还必要 bridge 转发到 eth0 网卡上。于是吾们得掀开下面这两个转发有关的配置

# sysctl net.ipv4.conf.all.forwarding=1  # iptables -P FORWARD ACCEPT 

不过这个时候,还存在一个题目。那就是外部的机器并不意识 192.168.0.* 这个网段的 ip。它们之间都是经历 10.153.*.* 来进走通信的。设想下吾们做事中的电脑上异国外网 IP 的时候是如何平常上网的呢?外部的网络只意识外网 IP。没错,那就是吾们上面说的 NAT 技术。

吾们这次的需求是实现内部虚拟网络访问外网,于是必要行使的是 SNAT。它将 namespace 乞求中的 IP(192.168.0.2)换成外部网络意识的 10.153.*.*,进而达到平常访问外部网络的凶果。

# iptables -t nat -A POSTROUTING -s 192.168.0.0/24 ! -o br0 -j MASQUERADE 

来再 ping 一下试试,欧耶,通了!

# ip netns exec net1 ping 10.153.*.*  PING 10.153.*.* (10.153.*.*) 56(84) bytes of data.  64 bytes from 10.153.*.*: icmp_seq=1 ttl=57 time=1.70 ms  64 bytes from 10.153.*.*: icmp_seq=2 ttl=57 time=1.68 ms 

这时候吾们能够开启 tcpdump 抓包查望一下,在 bridge 上抓到的包吾们能望到照样原首的源 IP 和 现在标 IP。

再到 eth0 上查望的话,源 IP 已经被替换成可和外网通信的 eth0 上的 IP 了。

至此,容器就能够经历宿主机的网卡来访问外部网络上的资源了。吾们来总结一下这个发送过程

3. 盛开容器端口

吾们再考虑另外一个需求,那就是把在这个命名空间内的服务挑供给外部网络来行使。

和上面的题目雷同,吾们的虚拟网络环境中 192.168.0.2 这个 IP 外界是不意识它的。只有这个宿主机晓畅它是谁。于是吾们同样还必要 NAT 功能。

这次吾们是要实现外部网络访问内部地址,于是必要的是 DNAT 配置。DNAT 和 SNAT 配置中有一个纷歧样的地方就是必要清晰指定容器中的端口在宿主机上是对答哪个。比如在 docker 的行使中,是经历 -p 来指定端口的对答有关。

# docker run -p 8000:80 ... 

吾们经历如下这个命令来配置 DNAT 规则

# iptables -t nat -A PREROUTING  ! -i br0 -p tcp -m tcp --dport 8088 -j DNAT --to-destination 192.168.0.2:80 

这边外示的是宿主机在路由之前判定一下倘若流量不是来自 br0,并且是访问 tcp 的 8088 的话,那就转发到 192.168.0.2:80 。

在 net1 环境中启动一个 Server

# ip netns exec net1 nc -lp 80 

外部选一个ip,比如 10.143.*.*, telnet 连一下 10.162.*.* 8088 试试,通了!

# telnet 10.162.*.* 8088  Trying 10.162.*.*...  Connected to 10.162.*.*.  Escape character is '^]'. 

开启抓包, # tcpdump -i eth0 host 10.143.*.*。可见在乞求的时候,现在标是宿主机的 IP 的端口。

但数据包到宿主机制定栈以后命中了吾们配置的 DNAT 规则,宿主机把它转发到了 br0 上。在 bridge 上原由异国那么众的网络流量包,于是不必过滤直接抓包就走,# tcpdump -i br0。

在 br0 上抓到的现在标 IP 和端口是已经替换过的了。

bridge 自然晓畅 192.168.0.2 是 veth 1。于是,在 veth1 上监听 80 的服务就能收到来自外界的乞求了!吾们来总结一下这个授与过程

三、总结

现在业界已经有许众公司都迁移到容器上了。吾们的开发写出来的代码也许率是要运走在容器上的。因此深切理解容器网络的做事原理专门的主要。这有云云异日遇到题目的时候才晓畅该如何动手处理。

本文起头吾们先是浅易介绍了 veth、bridge、namespace、路由、iptables 等基础知识。Veth 实现连接,bridge 实现转发,namespace 实现阻隔,路由外限制发送时的设备选择,iptables 实现 nat 等功能。

接着基于以上基础知识,吾们采用纯手工的手段搭建了一个虚拟网络环境。

这个虚拟网络能够访问外网资源,也能够挑供端口服务供外网来调用。这就是 Docker 容器网络做事的基本原理。

整个实验吾打包写成一个 Makefile,放到了这边:https://github.com/yanfeizhang/coder-kung-fu/tree/main/tests/network/test07

末了,吾们再扩展一下。今天吾们商议的题目是 Docker 网络通信的题目。Docker 容器经历端口映射的手段挑供对外服务。外部机器访问容器服务的时候,照样必要经历容器的宿主机 IP 来访问。

在 Kubernets 中,对跨主网络通信有更高的请求,要分歧宿主机之间的容器能够直接互联互通。于是 Kubernets 的网络模型也更为复杂。

【编辑选举】

鸿蒙官方战略配相符共建——HarmonyOS技术社区 手摸手学会搭建一个 TS+Rollup 的初首开发环境 14天学习Flask Web框架开发实战 数据分析值-爬虫开发实战 数据分析-Mysql详解与开发实战 右键珍藏!2021 Google 开发者大会怎么望?
posted @ 21-11-29 12:29 作者:admin  阅读:

Powered by 白虎美少女-被窝影院手机版免费手机官网-极品白虎实战高清视频 @2018 RSS地图 HTML地图

Copyright 站群 © 2013-2021 365建站器 版权所有