如何构造你自己的容器---3
金蝶云社区-墨家总院
墨家总院
58人赞赏了该文章 255次浏览 未经作者许可,禁止转载编辑于2018年12月25日 17:01:22

(本文独家发布在金蝶云社区上)

前言

这篇文章将介绍NET namespace。

NET namespace

顾名思义,”NET namespace” 主要用来实现网络接口的虚拟化,也叫做网络接口的隔离技术,再加上“user/group”的标识符以获得更高的透明度。

我们这次先不像前几篇文章一样直接将clone系统调用和相关标记位的代码罗列出来,我计划把这块往后放一放。我们先看一下一个功能强大的网络工具”iproute2“,堪称网络管理界的的瑞士军刀。如果你到现在都还没有用过这个工具,我强烈建议你先安装一个。如果你不想这么做,你可以直接跳过下面这一小部分,直接去看完整的代码示例。

iproute2 范例

先简单介绍一下iproute2:他是用于控制Linux中的TCP / IP网络和流量控制的实用程序的集合。

然后,让我们用这个工具看看现在有哪些网络接口。

ip link list

它会像这样输出:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
    link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DORMANT qlen 1000
    link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff


显然没有什么值得期待的事情发生。首先我有一个正在工作的loopback,然后我也连着我的无线网络。


现在,我们创建一个网络的命名空间,名字叫“demo”,并从内部运行和之前同样的命令:

# create a network namespace called "demo"
ip netns add demo
# exec "ip link list" inside the namespace
ip netns exec demo ip link list

输出如下:

1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

上面的输出仅有一个loopback,而且他的状态是down。更有趣的是,它是完全和主loopback隔离的。也就是说,在这个命名空间下的绑定了这个loopback的任何应用程序只能和这个命名空间下的应用程序进行通信。正好与IPC命名空间完全相同的隔离级别。


那么,接下来我该如何于外界的互联网通信呢?


有很多种方案。最简单的也是最常用的一个方案是在主端与客户端之间创建一个点对点的隧道。我们伟大的Linux kernel也提供了一些备选方案。我个人建议使用“veth”接口,因为他和目前的生态系统结合的最好,比如前面提到的iproute2.同时这个方案也是被用在LXC项目中,相关代码也经过了非常严格的测试,其最初其实来自于openvz项目。另一个可选方案是“etun”驱动,他在概念上和另外一个方案类似,但是我不确认有哪些项目用到了他,在此不赘述了。


“veth”和“etun”方案都是创建一对虚拟的接口,而且和当前namespace中的另一个连接上。你可以选择一个,并将他移动到目标namespace中去,以此得到一个通讯频道(channel)。为了便于理解,你可以将其想象成复杂的粒子运动。


下一步,给它们一个IP,设置为up,然后ping它!如下的bash会话(session),能够完成这件事:

# Create a "demo" namespace
ip netns add demo
# create a "veth" pair
ip link add veth0 type veth peer name veth1
# and move one to the namespace
ip link set veth1 netns demo
# configure the interfaces (up + IP)
ip netns exec demo ip link set lo up
ip netns exec demo ip link set veth1 up
ip netns exec demo ip addr add 169.254.1.2/30 dev veth1
ip link set veth0 up
ip addr add 169.254.1.1/30 dev veth0


如果您需要使用“veth”技术从“guest”系统访问Internet,则可以设置masquerding,通常称为“NAT”。 以同样的方式,要使一个网络服务器监听命名空间内的80端口号,而且是直接在主接口上监听,可以使用通常称为端口“转发”的“DNAT”。 我会把它留给读者。


下面是一个基本的例子:

# make sure ip forwarding is enabled
echo 1 > /proc/sys/net/ipv4/ip_forward
# enable Internet access for the namespace, assuming you ran the previous example
iptables -t nat -A POSTROUTING -i veth0 -j  MASQUERADE
# Forward main ":80" to guest ":80"
iptables -t nat -A PREROUTING -d <your main ip>/32 -p tcp --dport 80 -j  DNAT --to-destination  169.254.1.2:80

代码实例

现在让我们用之前章节的传统代码框架来实现一下上面进行的一些实验。即将CLONE_NEWNET标记添加到clone系统调用。为了简化,我们通过system()直接调用“ip”命令。

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#define STACK_SIZE (1024 * 1024)
// sync primitive
int checkpoint[2];
static char child_stack[STACK_SIZE];
char* const child_args[] = {
    "/bin/bash",
    NULL
};
int child_main(void* arg) {
    char c;
    // init sync primitive
    close(checkpoint[1]);
    // setup hostname
    printf(" - [%5d] World !\n", getpid());
    sethostname("In Namespace", 12);
    // remount "/proc" to get accurate "top" && "ps" output
    mount("proc", "/proc", "proc", 0, NULL);
    // wait for network setup in parent
    read(checkpoint[0], &c, 1);
    // setup network
    system("ip link set lo up");
    system("ip link set veth1 up");
    system("ip addr add 169.254.1.2/30 dev veth1");
    execv(child_args[0], child_args);
    printf("Ooops\n");
    return 1;
}
int main() {
    // init sync primitive
    pipe(checkpoint);
    printf(" - [%5d] Hello ?\n", getpid());
    int child_pid = clone(child_main, child_stack+STACK_SIZE,
      CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, NULL);
    // further init: create a veth pair
    char* cmd;
    asprintf(&cmd, "ip link set veth1 netns %d", child_pid);
    system("ip link add veth0 type veth peer name veth1");
    system(cmd);
    system("ip link set veth0 up");
    system("ip addr add 169.254.1.1/30 dev veth0");
    free(cmd);
    // signal "done"
    close(checkpoint[1]);
    waitpid(child_pid, NULL, 0);
    return 0;
}

试着运行下:

alexandere@mohism-Desktop:~/src/container$ gcc -Wall main.c -o ns && sudo ./ns
 - [22094] Hello ?
 - [    1] World !
root@In Namespace:~/src/container$ # run a super-powerful server, fully isolated
root@In Namespace:~/src/container$ nc -l 4242
Hi !
Bye...
root@In Namespace:~/src/container$ exit
alexander@mohism-Desktop:~/src/container$ # done !

如果在另外一个终端按照如下执行,我们就会看到上述结果。

alexander@mohism-Desktop:~$ nc 169.254.1.2 4242
Hi !    
Bye...
alexander@mohism-Desktop:~$

为了能够在网络虚拟化上走得更远,你可以了解下Linux内核最近引入的接口类型:macvlan,vlan,vxlans,…


赞 58