前言

在上个版本中,我们实现了容器的基本操作。这一次将实现容器网络部分。

Hook

纵观OCI Runtime标准,并没有定义网络标准。根据学习和推测,我认为它是通过hook实现的网络设置。

Hook就是一些在容器特定生命周期被调用的程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
"hooks": {
"prestart": [
{
"path": "/usr/bin/fix-mounts",
"args": ["fix-mounts", "arg1", "arg2"],
"env": [ "key1=value1"]
},
{
"path": "/usr/bin/setup-network"
}
],
"createRuntime": [
{
"path": "/usr/bin/fix-mounts",
"args": ["fix-mounts", "arg1", "arg2"],
"env": [ "key1=value1"]
},
{
"path": "/usr/bin/setup-network"
}
],
"createContainer": [
{
"path": "/usr/bin/mount-hook",
"args": ["-mount", "arg1", "arg2"],
"env": [ "key1=value1"]
}
],
"startContainer": [
{
"path": "/usr/bin/refresh-ldcache"
}
],
"poststart": [
{
"path": "/usr/bin/notify-start",
"timeout": 5
}
],
"poststop": [
{
"path": "/usr/sbin/cleanup.sh",
"args": ["cleanup.sh", "-f"]
}
]
}

如这个例子所示,在创建容器时,我们让Runtime调用setup-network程序。

Persistent Namespace

在第一个版本里,我们并没有在创建容器时调用pviot_root,而是在运行容器时才更换系统目录,创建新的命名空间。这将无法满足createContainer hook的要求。

1
(Executed in Container NS) During the create operation, after the runtime environment has been created and before the pivot root or any equivalent operation.

要实现这个hook,我们要在创建容器时就创建命名空间,并且允许hook程序对这些命名空间做一些操作。当我们运行容器时,之前的改动都必须还在,所以这些命名空间必须被持久化。

根据Linux手册,命名空间存放在/proc/<PID>/ns/*flie,当进程退出时,这些文件也就释放掉了,除非这些文件被bind-mount到另外的地方。

mount("/proc/pid/ns/net", "/home/ubuntu/container/ns/net", MS_BIND, nullptr)

之后,可以通过setns函数来恢复这些命名空间。

其中困难的地方在于

  1. mnt ns必须被挂在Private mount point
  2. pid ns没有必要持久化,必须保持容器进程运行,再将hook程序加入对应pid ns。每次容器运行,都创建新的pid ns

Network

Network Hook

Github上有一个现成的hook供我们使用,叫做netns。我们可以先用netns验证runtime工作正常,以后再实现自己的hook,从而达成全部自己实现的目标。

通过netns源码可知,在启动netns时,runtime需要将容器的status传递到netns的stdin。因为我在上一版本中,已经实现了将status保存为文件,这里只要将status.file打开,并设置fd为stdin,之后调用exec运行hook,将fd传递给子进程,就可以了。

1
2
3
4
5
6
7
8
// posix ensure it opens the least available fd
close(stdin);
open("status.json");

// or
int fd = open("status.json");
dup2(fd, stdin);
close(fd);

status里带有pid,netns通过访问/proc/pid/ns,获取到目标network namespace,再将创建的veth link过去。

netns默认没有打开bridge的转发功能。

1
2
3
4
sudp ifconfig <bridgename> up
sudo iptables -A FORWARD -o br0 -j ACCEPT
sudo iptables -A FORWARD -i br0 -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s <subnet> ! -o <bridgename> -j MASQUERADE