前言
namespace
是实现容器隔离的基础.namespace
的本质就是把原来所有进程全局共享的资源拆分成了很多个一组一组进程共享的资源.
root@nicktming:~# ls -l /proc/self/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 27 21:57 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 27 21:57 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 27 21:57 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 27 21:57 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 27 21:57 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 27 21:57 uts -> uts:[4026531838]
接下来将用实践的例子来简单看一下这些
namespace
的作用.
后面的例子需要用到unshare
命令
root@nicktming:~# unshare
Usage:
unshare [options] <program> [args...]
Options:
-h, --help usage information (this)
-m, --mount unshare mounts namespace
-u, --uts unshare UTS namespace (hostname etc)
-i, --ipc unshare System V IPC namespace
-n, --net unshare network namespace
For more information see unshare(1).
UTS Namespace
UTS Namespace
主要用来隔离nodename
和domainname
两个系统标识.
终端执行例子
root@nicktming:~# echo $$
13563
root@nicktming:~# ls -l /proc/13563/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 27 22:34 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 27 22:34 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 27 22:34 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 27 22:34 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 27 22:34 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 27 22:34 uts -> uts:[4026531838]
root@nicktming:~# unshare -u /bin/sh
# echo $$
14029
# ps -ef | grep 14029
root 14029 13563 0 22:35 pts/2 00:00:00 /bin/sh
root 14091 14029 0 22:36 pts/2 00:00:00 ps -ef
root 14092 14029 0 22:36 pts/2 00:00:00 grep 14029
# ls -l /proc/14029/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 27 22:36 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 27 22:36 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 27 22:36 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 27 22:36 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 27 22:36 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 27 22:36 uts -> uts:[4026532166]
# hostname
nicktming
# hostname -b bird
# hostname
bird
# uname -a
Linux bird 3.13.0-128-generic #177-Ubuntu SMP Tue Aug 8 11:40:23 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
由上可以看到进程
13563
中启动了一个子进程14029
并且有uts namespace
, 所以可以看到这父子进程共享了ipc mnt net pid user namespace
,但是并不在同一个uts namspace
中.
打开另外一个终端执行.
root@nicktming:~# hostname
nicktming
root@nicktming:~# uname -a
Linux nicktming 3.13.0-128-generic #177-Ubuntu SMP Tue Aug 8 11:40:23 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
可以看到在
14029
进程新建的一个uts namespace
里改变hostname
并不会影响主机里面的hostname
, 更不会影响到其他的namespace
中的hostname
, 进而达到一种隔离的状态.
go实现
go
实现只需要在启子进程的时候在SysProcAttr
定义中加入需要创建的namespace
即可,uts namespace
对应的是syscall.CLONE_NEWUTS
.
func main() {
cmd := exec.Command("/bin/sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Printf("Run error:%v\n", err)
log.Fatal(err)
}
}
执行如下:
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# hostname
nicktming
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# go run utsNamespace.go
# hostname
nicktming
# hostname -b bird
# hostname
bird
打开另外一个终端执行
hostname
,可以看到效果与上面终端例子一样.
root@nicktming:~# hostname
nicktming
MNT Namespace
Mount Namespace
用来隔离各个进程看到的挂载点视图。在不同Namespace
的进程中,看到的文件系统层次是不一样的。在Mount Namespace
中调用mount()
和umount()
仅仅只会影响当前Namespace
内的文件系统,而对全局的文件系统是没有影响的.
终端执行例子
root@nicktming:~# echo $$
8217
root@nicktming:~# ls -l /proc/8217/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 28 21:23 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 28 21:23 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 28 21:23 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 28 21:23 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 28 21:23 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 28 21:23 uts -> uts:[4026531838]
root@nicktming:~# mkdir -p /tmp/test_mnt_namespace
root@nicktming:~# unshare --mount /bin/sh
# echo $$
8493
# ls -l /proc/8493/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 28 21:25 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 28 21:25 mnt -> mnt:[4026532166]
lrwxrwxrwx 1 root root 0 Mar 28 21:25 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 28 21:25 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 28 21:25 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 28 21:25 uts -> uts:[4026531838]
# mount -t tmpfs tmpfs /tmp/test_mnt_namespace
# cd /tmp/test_mnt_namespace
# echo "pid:8493 mnt namespace" > test01.txt
# ls
test01.txt
可以看到父子进程不在同一个
mnt namespace
.重新打开一个terminal
.
root@nicktming:~# ls -l /tmp/test_mnt_namespace/
total 0
root@nicktming:~# unshare --mount /bin/sh
# ls /tmp/test_mnt_namespace
# mount -t tmpfs tmpfs /tmp/test_mnt_namespace
# cd /tmp/test_mnt_namespace
# echo $$
8996
# echo "pid:8996 mnt namespace" > test02.txt
# ls
test02.txt
go实现
利用
syscall.CLONE_NEWNS
这个字段.
func main() {
cmd := exec.Command("/bin/sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Printf("Run error:%v\n", err)
log.Fatal(err)
}
}
PID Namespace
PID Namespace
是用来隔离进程ID
的. 同样一个进程在不同的PID Namespace
中有不同的pid
.
go实现
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# echo $$
8848
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# go run pidNamespace.go
# echo $$
1
# ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 8848 8796 0 80 0 - 5343 wait pts/2 00:00:00 bash
4 S 0 12834 8848 0 80 0 - 45700 futex_ pts/2 00:00:00 go
4 S 0 12852 12834 0 80 0 - 810 wait pts/2 00:00:00 pidNamespace
0 S 0 12855 12852 0 80 0 - 1111 wait pts/2 00:00:00 sh
0 R 0 12908 12855 0 80 0 - 2185 - pts/2 00:00:00 ps
# mount -t proc proc /proc
# ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 0 1 0 0 80 0 - 1111 wait pts/2 00:00:00 sh
0 R 0 4 1 0 80 0 - 2185 - pts/2 00:00:00 ps
可以看到进行该子进程进入了一个新的
mnt pid namespace
, 在宿主机中该子进程的pid
是12855
,但是在其自己的namespace
中它的pid
是1
.
NET Namespace
Network Namespace
是用来隔离网络设备、IP
地址端口等网络械的Namespace
.Network Namespace
可以让每个容器拥有自己独立的(虚拟的)网络设备,而且容器内的应用可以绑定到自己的端口,每个Namespace
内的端口都不会互相冲突。在宿主机上搭建网桥后,就能很方便地实现容器之间的通信,而且不同容器上的应用可以使用相同的端口。
终端执行例子
root@nicktming:~# echo $$
1222
root@nicktming:~# ls -l /proc/1222/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 28 22:34 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 28 22:34 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 28 22:34 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 28 22:34 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 28 22:34 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 28 22:34 uts -> uts:[4026531838]
root@nicktming:~# unshare --net /bin/sh
# echo $$
1353
# ls -l /proc/1353/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 28 22:35 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 28 22:35 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 28 22:35 net -> net:[4026532161]
lrwxrwxrwx 1 root root 0 Mar 28 22:35 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 28 22:35 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 28 22:35 uts -> uts:[4026531838]
# ifconfig
# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# ping 127.0.0.1
connect: Network is unreachable
# ip link set lo up
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
# ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.016 ms
可以看到父子进程在不同的
network namespace
中.
go实现
用
syscall.CLONE_NEWNET
进行标识.
func main() {
cmd := exec.Command("/bin/sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNET,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Printf("Run error:%v\n", err)
log.Fatal(err)
}
}
执行结果如下:
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# go run netNamespace.go
# ifconfig
# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
USER Namespace
User Namespace
主要是隔离用户的用户组ID
. 也就是说, 一个进程的User ID
和Group ID
在User Namespace
内外可以是不同的。比较常用的是,在宿主机上以一个非root
用户运行创建一个User Namespace
, 然后在User Namespace 里面却映射成root
用户。这意味着 这个进程在User Namespace
里面有root
权限,但是在User Namespace
外面却没有root
的权限。从Linux Kernel 3.8
开始,非root
进程也可以创建User Namespace
, 并且此用户在Namespace
里面可以被映射成root
,且在Namespace
内有root
权限。
func main() {
cmd := exec.Command("/bin/sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUSER,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Printf("Run error:%v\n", err)
log.Fatal(err)
}
}
执行结果如下:
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# id
uid=0(root) gid=0(root) groups=0(root)
root@nicktming:~/go/src/github.com/nicktming/mydocker/test/namespace# go run userNamespace.go
$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
IPC Namespace
IPC Namespace
用来隔离System V IPC
和POSIX message queues
.
终端执行例子
root@nicktming:~# ipcs
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
------ Message Queues --------
key msqid owner perms used-bytes messages
root@nicktming:~# echo $$
3792
root@nicktming:~# ls -l /proc/3792/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 28 22:53 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 28 22:53 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 28 22:53 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 28 22:53 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 28 22:53 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 28 22:53 uts -> uts:[4026531838]
root@nicktming:~# unshare --ipc /bin/sh
# echo $$
3861
# ls -l /proc/3861/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 28 22:54 ipc -> ipc:[4026532160]
lrwxrwxrwx 1 root root 0 Mar 28 22:54 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 28 22:54 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 28 22:54 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 28 22:54 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 28 22:54 uts -> uts:[4026531838]
# ipcs
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
------ Message Queues --------
key msqid owner perms used-bytes messages
# ipcmk -Q
Message queue id: 0
# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x695dae8c 0 root 644 0 0
# ipcs
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
------ Message Queues --------
key msqid owner perms used-bytes messages
0x695dae8c 0 root 644 0 0
打开另外一个
terminal
,发现宿主机上并没有刚刚创建的queue
,所以ipc namespace
隔离已经成功.
root@nicktming:~# ipcs
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------
key semid owner perms nsems
------ Message Queues --------
key msqid owner perms used-bytes messages
go实现
func main() {
cmd := exec.Command("/bin/sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWIPC,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Printf("Run error:%v\n", err)
log.Fatal(err)
}
}
参考
1. 自己动手写docker.(基本参考此书,加入一些自己的理解,加深对
docker
的理解)
全部内容
mydocker.png
1. [mydocker]---环境说明
2. [mydocker]---urfave cli 理解
3. [mydocker]---Linux Namespace
4. [mydocker]---Linux Cgroup
5. [mydocker]---构造容器01-实现run命令
6. [mydocker]---构造容器02-实现资源限制01
7. [mydocker]---构造容器02-实现资源限制02
8. [mydocker]---构造容器03-实现增加管道
9. [mydocker]---通过例子理解存储驱动AUFS
10. [mydocker]---通过例子理解chroot 和 pivot_root
11. [mydocker]---一步步实现使用busybox创建容器
12. [mydocker]---一步步实现使用AUFS包装busybox
13. [mydocker]---一步步实现volume操作
14. [mydocker]---实现保存镜像
15. [mydocker]---实现容器的后台运行
16. [mydocker]---实现查看运行中容器
17. [mydocker]---实现查看容器日志
18. [mydocker]---实现进入容器Namespace
19. [mydocker]---实现停止容器
20. [mydocker]---实现删除容器
21. [mydocker]---实现容器层隔离
22. [mydocker]---实现通过容器制作镜像
23. [mydocker]---实现cp操作
24. [mydocker]---实现容器指定环境变量
25. [mydocker]---网际协议IP
26. [mydocker]---网络虚拟设备veth bridge iptables
27. [mydocker]---docker的四种网络模型与原理实现(1)
28. [mydocker]---docker的四种网络模型与原理实现(2)
29. [mydocker]---容器地址分配
30. [mydocker]---网络net/netlink api 使用解析
31. [mydocker]---网络实现
32. [mydocker]---网络实现测试
网友评论