自己动手写docker
<<自己动手写docker>>阅读实践笔记
docker理论基础这部分不再赘述,可以看我这篇文章一文带你全方位入门docker。
此次实践使用的IDE为Goland,操作系统为mac,代码运行环境为Cent OS7,内核版本为3.10.0-957.21.3.el7.x86_64
开始前,需要修改Goland,Preferences–>Go–>OS改为linux,内核版本为。否则代码编写时一些linux only的系统调用会报错。
https://golang.org/pkg/syscall/#SysProcAttr
1. 实现六个namespace隔离
1.1. 实现UTS namespace隔离
通过syscall.CLONE_NEWUTS
创建一个新的UTS namespace
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("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.Fatal(err)
}
}
在centos上运行此代码go run 1.uts_ns.go
进入一个交互式的环境
通过pstree -pl
查询进程树
$ pstree -pl | grep go
|-sshd(21453)---sshd(27698)---bash(27701)---go(16497)-+-1.uts_ns(16514)-+-sh(16517)-+-grep(16564)
这时去查询父进程和子进程的UTS namespace
sh-4.2# ls /proc/16514/ns/uts -al
lrwxrwxrwx 1 root root 0 7月 6 13:41 /proc/16514/ns/uts -> uts:[4026531838]
sh-4.2# ls /proc/16517/ns/uts -al
lrwxrwxrwx 1 root root 0 7月 6 13:41 /proc/16517/ns/uts -> uts:[4026532283]
可以看到不在同一个UTS namespace中,UTS namespace主要用来隔离主机名和域名,现在我们来修改主机名进行验证。
sh-4.2# hostname newuts
sh-4.2# hostname
newuts
再打开一个终端查看宿主机主机名可以验证主机名并没有被内部程序更改
[root@external_node ~]# hostname
external_node
1.2. 实现IPC namespace隔离
IPC namespace用来隔离System V IPC 和 POSIX message queues。通过syscall.CLONE_NEWIPC
,创建一个新的UTS namespace
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil{
log.Fatal(err)
}
}
通过ipcmk
创建ipc资源
[root@external_node docker_study]# ipcmk -Q
消息队列 id:0
[root@external_node docker_study]# ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0xc2bc06da 0 root 644 0 0
运行我们的代码,在新的namespace中,看不到宿主机的消息。
sh-4.2# ipcs -q
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
1.3. 实现PID namespace隔离
PID namespace用来隔离进程的ID,通过syscall.CLONE_NEWPID
来实现。
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil{
log.Fatal(err)
}
}
运行程序
[root@external_node docker_study]# go run 3.pid_ns.go
sh-4.2# pstree -pl
...
│ └─sshd(27698)───bash(27701)───go(25623)─┬─3.pid_ns(25640)─┬─sh(25643)───pstree(25740)
...
sh-4.2# echo $$
1
当前的namespace下进程ID为1,在宿主机上为25640
1.4. 实现Mount namespace隔离
Mount namespace用来隔离各个进程看到的挂载点视图,不同的namespace看到的文件系统层次是不一样的,通过 syscall.CLONE_NEWNS
来实现
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil{
log.Fatal(err)
}
}
运行我们的代码
[root@external_node docker_study]# go run 4.mount_ns.go
sh-4.2# ls /proc/
1 15 22 27438 3141 9976 irq schedstat
10 15826 23 27442 32 acpi kallsyms scsi
100 15917 23243 27514 33 buddyinfo kcore self
11 16 23301 27520 41 bus keys slabinfo
1103 16755 24 27537 43 cgroups key-users softirqs
1175 16771 24820 27698 44 cmdline kmsg stat
1182 16899 25077 27701 45 consoles kpagecount swaps
1183 17 2571 2879 46 cpuinfo kpageflags sys
1265 18 2573 29103 5 crypto loadavg sysrq-trigger
1266 18411 2574 29108 59 devices locks sysvipc
12717 18734 2575 29125 666 diskstats mdstat timer_list
13 18737 2577 2946 680 dma meminfo timer_stats
1362 18870 26168 2953 689 driver misc tty
13875 19 26774 2962 696 execdomains modules uptime
13880 194 27191 3 699 fb mounts version
1389 2 27389 30 7 filesystems mtrr vmallocinfo
13893 20 27391 3032 8 fs net vmstat
1391 21 27401 3038 9 interrupts pagetypeinfo zoneinfo
13910 21157 27421 31 949 iomem partitions
14 21453 27424 3139 9702 ioports sched_debug
查看/proc的内容。proc是一个文件系统,提供额外的机制,可以通过内核和内核模块将信息发送给进程。mount()
和unmount()
仅仅影响当前namespace内的文件系统,所以们现在将宿主机/proc挂在到当前namespace,再查看/proc就是当前namespace的内容了
sh-4.2# mount -t proc proc /proc
sh-4.2# ls /proc/
1 devices ioports locks sched_debug sysvipc
4 diskstats irq mdstat schedstat timer_list
acpi dma kallsyms meminfo scsi timer_stats
buddyinfo driver kcore misc self tty
bus execdomains keys modules slabinfo uptime
cgroups fb key-users mounts softirqs version
cmdline filesystems kmsg mtrr stat vmallocinfo
consoles fs kpagecount net swaps vmstat
cpuinfo interrupts kpageflags pagetypeinfo sys zoneinfo
crypto iomem loadavg partitions sysrq-trigger
sh-4.2# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:33 pts/0 00:00:00 sh
root 5 1 0 14:36 pts/0 00:00:00 ps -ef
1.5. 实现User namespace 隔离
User namespace 主要是隔离用户的用户组ID,一个进程的User ID和Group ID在User namespace内外可以是不同的。在宿主机创建一个普通用户,在别的User namespace可以映射为root用户。通过syscall.CLONE_NEWUSER
来实现。
centos7需要先开启User namespace,然后重启。
grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
reboot
echo 640 > /proc/sys/user/max_user_namespaces
代码如下
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID: 10000,
HostID: 0,
Size: 1,
},
},
GidMappings: []syscall.SysProcIDMap{
{
ContainerID: 10000,
HostID: 0,
Size: 1,
},
},
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil{
log.Fatal(err)
}
os.Exit(-1)
}
运行代码
[root@external_node docker_study]# go run 5.user_ns.go
sh-4.2$ id
uid=10000 gid=10000 组=10000
1.6. 实现Network namespace隔离
Network namespace用来隔离网络设备,IP地址端口等网络栈。Network namespace可以让每个容器拥有自己独立的网络设备。
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER | syscall.CLONE_NEWNET,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil{
log.Fatal(err)
}
os.Exit(-1)
}
运行代码
[root@external_node docker_study]# go run 6.network_ns.go
sh-4.2$ ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
sh-4.2$ ifconfig
宿主机的网络设备,在当前namespace中都不可见。