虚拟机是购买公寓的话,容器就好比是一个公寓里面合租的单个小单间,有一定隔离,但并不是那么的好,云计算解决了基础资源层的弹性伸缩,但是没有解决Paas层应用随着基础资源伸缩带来的快速部署的问题

容器,Container,翻译过来可以表明为集装箱,容器的思想变成了软件交付的集装箱,可以打包和标准化部署

没有集装箱的时候,假设要将货物从A运到B,中间经过三个码头,在此过程中,每次都要讲货物弄得一团糟

如果有了集装箱,那么就好办了,可以将所有的货物打包到了一起,每次换船的时候,就方便整体移动了,这就是集装箱的应用

图片

是怎么打包的呢?

主要是两个技术,一个是看起来隔离的技术,称为namespace,使用这个技术让应用看到的是不同的IP地址,用户控件,进程号,另一种是用起来隔离的技术,称为cgroup,让应用只能使用一部分的硬件资源

利用这两个技术,我们就可以打包镜像了,所谓的镜像,就是将打包的时候的样子进行了一个保存,这样,无论在哪里运行这个镜像都可以了

图片

那么,如何打包的,我们具体看一下

1.命名空间 namespace

namespace就是命名空间,在很多面向对象的程序设计中,都有着命名空间,为每个功能保留自己的命名空间,在不同的空间里面,类名相同,可以避免冲突

在Linux下,很多的资源是全局的,比如进程的ID全局唯一,网络亦是如此,有着全局的路由表,但是一个Linux上跑着多个进程的时候,可能会出现每个进程对应不同的路由,进程会冲突,那么就需要将进程放在一个独立的namespace

网络的namespace由ip netns命令操作,可以创建,删除,查询 namesapce

之前的虚拟机的图,我们重新来看下

图片

我们创建一个routerns,可以出现一个独立的网络空间,可以尽情的设置自己的规则

ip netns add routerns

既然是路由器,就需要打开forward开关

ip netns exec routerns sysctl -w net.ipv4.ip_forwareed =1

这个exec就是进入这个网络空间,初始化iptables,这是要配置NAT规则

我们配置完成,进行保存了iptables的信息,并进行了重启

ip netns exec routerns iptables-save -c

ip netns exec routerns iptables-restore -c

这时候需要有一张网卡连到br0上,就需要创建一个网卡

ovs-vsctl –add – port br0 taprouter –set Interface taprouter type = internal –set Interface taprouter external -ids:iface-status=acctive;

这个时候,网络创建完成了,但是是放在了namespace外面,如果放进去呢?

ip link set taprouter netns routerns

来设置进去

然后需要给这个网卡配置一个IP地址,应该是虚拟机网络的网关地址,比如虚拟机的私网网段为192.168.1.0/24,设置的网关地址为192.168.1.1

ip netns exec routerns ip -4 addr add 192.168.1.1/24 brd 192.168.1.255 scope global dev taprouter

为了访问外网,需要另一个网卡连在外网网桥br-ex上,并且塞在namespace中

ovs-vsctl –add -port br-ex taprouterex — set Interface taprouterex type -internal –set Interface taprouterex external-ids:iface-status=active –set Interface taprouter external-ids:attached-mac=fa:16:3e:68:12:c0

ip link set taprouterex netns routerns

然后为这个网卡分配一个地址,这个地址和物理外网的网络在一个网段,假如物理外网为16.158.1.0/24,可以分配一个外网地址 16.158.1.100/24

ip netns exec routerns ip -4 addr add 16.158.1.100/24 brd 16.158.1.255 scope global dev taprouterex

然后是路由表的配置

ip netns exec routerns route -n

Kernel IP routing table

Destination   Gateway     Genmask     Flags Metric Ref  Use Iface

0.0.0.0     16.158.1.1  0.0.0.0     UG  0   0    0 taprouterex

192.168.1.0    0.0.0.0     255.255.255.0  U   0   0    0 taprouter

16.158.1.0  0.0.0.0     255.255.255.0  U   0   0    0 taprouterex

默认路由走的是私网的网段,去下面的taprouter

去16.158.1.0/24走的是公网的网段,去上面的taprouterex

如果是服务端部署在了虚拟机,需要让外网的客户端可以访问的到,那么会在外网网口NAT为私网的IP,方便访问到服务端

这就需要配置NAT规则

ip netns exec routerns iptables -t nat -nvL

Chain PREROUTING

target  prot opt  in  out  source  destination

DNAT  all  —  *  *  0.0.0.0/0 16.158.1.103 to:192.168.1.3

Chain POSTROUTING

target  prot opt  in  out  source   destination

SNAT  all  —  *  *  192.168.1.3  0.0.0.0/0 to:16.158.1.103

分别配置了SNAT和DNAT

SNAT将虚拟机的私网IP, 192.168.1.3 NAT为物理机的外网IP 16.158.1.103

DNAT将物理的外网16.158.1.103 NAT称为虚拟机私网 192.168.1.3

这样路由器就基本实现完成了

然后是cgroup机制

cgroup全程是control groups,是Linux内核的一种可以隔离,限制线程使用资源的机制

常见的隔离资源有

CPU,可以控制进程访问CPU

cpuset,多核的CPU,这个子系统会为进程分配单独的CPU和内存

memory子系统,设置进程的内存限制

blkio子系统,限制每个块设备的输入输出设置

net_cls使用等级识别符classid标记网络数据包,允许Linux流量控制的程序 tc 来识别从cgroup中生成的数据包

cgroup提供了一个虚拟文件系统,作为分组管理和各个子系统设置的用户的接口,使用cgroup,必须挂载cgroup文件系统,

我们假设挂载一个net_cls的文件系统

mkdir /sys/fs/cgroup/net_cls

mount -t cgroup -onet_cls net_cls /sys/fs/cgroup/net_cls

接下来配置了TC,因为net_cls和TC想配合使用

图片 然后设置规则,从1.2.3.4,并且访问port80的包,从1:10走,其他从1.2.3.4发来的包走1:11,不是的走默认的

tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 match ip dport 80 0xffff flowid 1:10

tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 flowid 1:11

然后根据cgroup来设定规则

tc filter add dev eth0 protocol ip parent 1:0 prio 1 handle 1: cgroup

然后接入了根节点后

我们有两个用户 a,b我们要对他们进行带宽的限制

我们创建了两个net_cls

mkdir /sys/fs/cgroup/net_cls/a

mkdir /sys/fs/cgroup/net_cls/b

用户a的启动进程ID为12345,把其放在net_cls/a/tasks文件中

设置用户b的启动进程ID为 12346,放在net_cls/b/tasks文件中

net_cls/a目录下,还有一个文件 net_cls_classid 准备写入 flowid 1:10

net_cls/目录下,同样有一个文件 net_cls_classis 准备写入 flowid 1:11

准备写入的是flowid 1:10,实际写入的是

0xAAAABBBB的值,AAAA对应的class中冒号前面的数字,BBBB对应后面的数字

也就是

echo 0x00010010 > /sys/fs/cgroup/net_cls/a/net_cls.classid

echo 0x00010011 > /sys/fs/cgroup/net_cls/b/net_cls.classid

这样用户a进程的包,会打上1:10这个标签,用户b的进城的包,会打上1:11的包,然后TC根据两个标签,分别走不同的分支

那么,容器网络中如何融入物理网络

如果docker run一个容器,那么拓补结构可以如下

图片

和虚拟机类似的是,容器中有个网卡,容器外有张网卡,容器外的网卡连到了docker0的网桥,通过这个网桥,实现了相互访问

这个网桥和虚拟机创建的网桥本质上一致

那么容器和虚拟机的不同在于连接容器和网桥的网卡不一致性,

因为虚拟机的场景下,有一个虚拟化的软件,来虚拟一个网卡,但是容器没法虚拟化出这个网卡

在Linux下,可以创建一对veth pair的网卡,一边发送包,另一边直接收到

我们首先创建这么一对

ip link add name veth1 mtu 1500 type veth peer name veth2 mtu 1500

一边放在了docker 0网桥上

ip link set veth1 master testbr

ip link set veth1 up

另一端放在容器中

但是放入容器,必然要知道容器的namespace

我们要知道这个容器的namespace,就是获取这个容器的pid

docker inspect ‘–format={{ .State.Pid }}’ test

默认Docker的网络的namespace不再默认路径下,需要我们做个软连接

rm -f /var/run/netns/12065

ln -s /proc/12605/ns/net /var/run/netns/12065

我们就可以直接塞进去了

ip link set veth2 netns 12065

然后进行了重命名

ip netns exec 12065 ip link set veth2 name eth0

然后给容器内部的网卡设置ip地址

ip netns exec 12065 ip addr add 127.17.0.2/16 dev eth0

ip netns exec 12065 ip link set eth0 up

一台机器内部访问已经好了,如何访问到外网呢?

这个流程可以通过虚拟机里面的桥接或者NAT模式,Docker默认使用了NAT模式,NAT模式分为了SNAT和DNAT

从容器内部的客户端访问外部网络中的服务器的细节基本如下

图片

这样,所有从容器内部发出的包,都要做地址伪装,将源IP伪装成物理网卡的IP地址,如果有多个容器,所有的容器共享有一个外网的IP地址,但是在conntrack表中,记录下这个出去的连接

返回结果的时候,到达了物理机,会根据conntrack表中的规则,取出原来的私网IP,通过DNAT将地址转换为私网ip地址,通过网桥docker0实现对内的访问

如果在容器内部属于一个服务,例如部署一个网站,提供给外部进行访问,通过Docker的端口映射,直接将容器内部的端口映射到物理机上

例如容器内部监听的80端口,可以通过Docker run的参数 -p 10080:80,将物理机上的10080端口和容器的80端口映射起来,当外部的客户端访问这个网站的时候,会访问到容器的80端口

图片

Docker 有两种方式,一种是通过docker-proxy的方式,监听10080,转换为80端口

/usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 10080 -container-ip 172.17.0.2 -container-port 80

一种是通过DNAT的方式,在A PREROUTING阶段加一个规则,将端口10080的DNAT变更为私有网络

-A DOCKER -p tcp -m tcp –dport 10080 -j DNAT –to-destination 172.17.0.2:80

本章总结

容器是一种比虚拟机更加轻量级的隔离方式,通过namespace和cgroup技术进行的资源隔离,namespace看起来隔离,cgroup 用于负责起来隔离

容器网络连接到物理网络和虚拟机很像,通过桥接来做到一个物理机上的容器来相互访问,访问外网,可以通过NAT来进行访问

课后思考

1.容器内的网路和物理机网络可以使用NAT的方式相互访问,如果这种方式用于部署应用,那么会出现什么问题?

2.虚拟机中,不同物理机上的容器需要相互通信,容器怎么做到的呢?

1.解答可以理解为,由于nat导致网络性能有所损耗,而且物理机的端口被占用了,容器多的时候,对于物理机的端口的占用更加的明显

2.跨物理机的容器调用,需要实现集群内的容器的唯一性IP,插件很多,比如flnnel macvlan等

跨物理机的容器通信,主要就是构建扁平化的网络模型,主要有cnm和cni模型

而且nat网络如果用于部署服务,会导致一个问题

别人看到的IP是nat之后的ip,例如服务注册这样的功能,导致服务注册获取的IP是nat之后的IP,而不是真实的IP

发表评论

邮箱地址不会被公开。 必填项已用*标注