说到docker,大家都懂。但是LXC可能就比较陌生。Docker的起源于LXC。LXC的英文全称是Linux Container,相比较其他虚拟机而言,是一种轻量级虚拟化技术,它介于Chroot(linux的一个改变根目录挂载点的机制)和完整开发的虚拟机之间。LXC不使用单独的内核资源,但是可以创建一个类似的Linux操作系统环境。
Linux Daemon(LXD)是一个轻量级容器管理程序,他是凌驾于LXC之上而衍生的一套外部管理工具。Docker也使用类似技术。LXD使用了LXC API来管理LXC,而且新增RESTful API。
这边文章通过研究LXC的隔离特征来说明容器的一些原理。
每个容器都有一套独立的Linux环境命名空间。命名空间的作用是对每一个环境做隔离,使用环境的用户来看,好像是一个新的机器环境。命名空间是linux内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。
一套独立的环境需要做到有用户id独立、进程id独立、root根目录独立、网络独立、UTS(UNIX Time-sharing System 的缩写,主机名和NIS域名)独立。
不同的 namespace 中用户可以有相同的 UID 和 GID,它们之间互相不影响。父子 namespace 之间可以进行用户映射,如父 namespace (宿主机)的普通用户映射到子 namespace (容器)的 root 用户,以减少子 namespace 的 root 用户操作父 namespace 的风险。user namespace 功能虽然在很早就出现了,但是直到 Linux kernel 3.8之后这个功能才趋于完善。
比如/etc/subuid文件描述了容器内的id和容器外id的对应关系。root:300000:65536的意思
是:root启动的容器,那么容器外是从300000开始到365536的范围对应着容器内1-65536的范围
每个Linux系统要求的uid可选范围都不一样,但是uid通常是由32位,也就是最大值可以是2^31-2(范围:1~4294967295)。路径/etc/login.defs文件的UID_MIN、UID_MAX限定了用户useradd新用户自己设置uid的最小值和最大值,2^31-1是个无效id,实验测试以下命令不能成功。
useradd -u 4294967296 test
Uid的取值区间范围作了划分,不同发行商的Linux系统有不一样的划分,但是一般是这么约定:
每个Linux系统要求的pid可选范围都不一样,最大值可以设置到 4194304 (= 222) 。
在64位可以设置到222差不多400w左右。Linux代码设计者认为这个数已经足够了。如果是32位,pid最大值是32768.进程id一旦赋予就不会改变。
每个系统的设置查看cat /proc/sys/kernel/pid_max。
我在ubuntu 16和centos 7分别得到了131072(= 217)和32768(= 215)。
Pid是顺序产生的。当pid到达最大值,它会从0继续开始找最近可用的pid,如果都没有pid,会报错。
以下测试了hostname在每个命名空间是独立存在的。
unshare --uts --fork bash
hostname//查看继承的原来hostname
hostname modified //修改成新的hostname
hostname //确认修改成功
eixt //退出
hostname //此时hostname还是原来的hostname
在每个容器所需要的最少网卡接口包括默认的lo回环link(这个有什么用):
除开lo,一般隔离的网络还有一对veth-pair。除了veth-pair,linux网卡方式还有其他
这个可以使用ip link help查看到。这里提一下,ip是个很强大的命令,包含但不止于ifconfig和route的功能,是个很强大的工具,link这里指的是链路层。对应的ip addr是网络层。
那么veth网卡是什么:veth:一对网络设备,包含两个设备,一个设备通过lxc.network.link网桥连接另一个网卡设备。
容器通过veth网卡对,veth 总是成对出现,两个veth网卡分别位于两个命名空间,实现容器内外的通信。在 veth 设备任意一端接收到的数据,都会从另一端发送出去。
还比如下面这个宿主机有两个容器,veth0和veth1是一对,veth2和veth3是一对。并且在宿主机的veth0和veth2设备通过NAT连接到外网。这个NAT是通过在iptables追加一个NAT的masquerade规则实现的。当然在发送数据前,需要经过iptables MASQUERADE
规则将源地址改成宿主机 ip,这样才能接收到响应数据包。而宿主机网卡接收到的数据会通过iptables DNAT
根据端口号修改目的地址和端口为容器的ip 和端口,然后根据路由规则发送到网桥 docker0 中,并最终由网桥 docker0 发送到对应的容器中。
一个veth网卡对和NAT桥接例子,左半部分是容器内,右半部分是宿主机器。eth0和vethK57NDU是一个veth对。发送或接收给一个veth的数据包相当于丢给另一个veth网卡。然后vethK57NDU是通过NAT桥接网络到lxcbr0.
进一步查看vethK57NDU和lxcbr0的桥接关系:
所以在容器内丢给eth0的数据包就是相当于丢给宿主机的lxdbr0。
还有个问题是,那么容器内他可能是访问8.8.8.8的因特网,那么这个lxdbr0并没有连接外网,那么数据包是怎么路由出去的。这里其实还有个NAT的MASQUERADE,通过这个规则,外网的数据包最终会通过有联网的ens33外网网卡发出去。
容器内的网络配置可以通过查看到,容器内执行lxc profile edit default
sudo ip netns add mynamespace //创建一个名字叫mynamespace的网络命名空间
sudo ip netns list mynamespace //列出这个命名空间
sudo ls -l /var/run/netns/ //在var目录下生成一个文件夹
total 0 -r-------- 1 root root 0 Nov 10 22:24 mynamespace
sudo ip netns exec mynamespace bash //进入这个命名空间
ip link add vethMYTEST type veth peer name eth0 //新增一个veth网卡对,两个名字分别叫vethMYTEST和eth0
ip link list //查看所有的网卡链路层,可以看到刚添加的两个设备
ip address add 10.0.3.78/24 dev eth0 //为eth0添加ip地址
ip link set eth0 up //启动eth0网络层
ip address list //查看所有的网卡ip层
ip link set vethMYTEST netns 1 //将vethMYTEST设备移出去,移到宿主机
exit //退出去到宿主机
ping -c 2 10.0.3.78 //宿主机ping mynamespace命名空间的eth0网卡,发现还不可达
sudo brctl addif lxcbr0 vethMYTEST //做网桥设置,把vethMYTEST连接到lxcbr0,此时ping到lxcbr0的数据包就可以到命名空间里的eth0
ping -c 2 10.0.3.78 //这时已经可达
sudo ip netns exec mynamespce bash//再次进入这个命名空间
ping 8.8.8.8 //内部发送给外部的包不可达
traceroute 8.8.8.8//并没有找到eth0网卡
ip route//观察没有到网关的路由默认规则
sudo route add default gw 10.0.3.1 eth0//新增一条到网关的路由默认规则
ping 8.8.8.8//ping测试成功
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。