背景
经过docker依赖的技术的了解,本文主要就是来动手实践一下。如何去简单的利用隔离技术来打造一个简单的容器。
容器的创建过程
根据这个思路编写如下示例代码
package main
import (
"golang.org/x/sys/unix"
"path/filepath"
"path"
"fmt"
"syscall"
"os"
"os/exec"
)
func main() {
switch os.Args[1] {
case "run":
parent()
case "child":
child()
default:
panic("what should I do")
}
}
func parent(){
fmt.Println("parent run ", os.Args)
cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[2:]...)...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS |
syscall.CLONE_NEWUTS |
syscall.CLONE_NEWIPC |
syscall.CLONE_NEWPID |
syscall.CLONE_NEWNET,
Unshareflags: syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println("ERROR", err)
os.Exit(1)
}
}
func pivotRoot(rootfs string) error {
//common.Exec("mount","--make-rprivate","/")
exec.Command("mount", "--make-rprivate","/").CombinedOutput()
if err := syscall.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
fmt.Errorf("Mount %s to itself error, %v", rootfs, err)
return err
}
putOld := path.Join(rootfs, ".pivotroot")
if err := os.Mkdir(putOld, 0777); err != nil {
fmt.Errorf("Failed to create putOld folder %s, error: %v", putOld, err)
return err
}
if err := syscall.PivotRoot(rootfs, putOld); err != nil {
fmt.Errorf("Failed to Pivot Rootfs %s, error: %v", rootfs, err)
return err
}
fmt.Println("PivotRoot done")
if err := os.Chdir("/"); err != nil {
return fmt.Errorf("chdir error %v", err)
}
old := path.Join("/", ".pivotroot")
if err := syscall.Unmount(old, syscall.MNT_DETACH); err != nil {
return fmt.Errorf("Unmount failed %v", err)
}
return os.Remove(old)
}
func child(){
fmt.Println("child run ", os.Args)
nsMount := "/var/run/netns/test_ns"
fd, err := syscall.Open(nsMount, syscall.O_RDONLY, 0)
if err != nil {
fmt.Printf("Unable to open: %v\n", err)
}
if err := unix.Setns(fd, syscall.CLONE_NEWNET); err != nil {
fmt.Printf("Setns system call failed: %v\n", err)
}
pwd, _ := os.Getwd()
rootfs := filepath.Join(pwd, "rootfs")
fmt.Println("rootfs ", rootfs)
if err := pivotRoot(rootfs); err != nil {
fmt.Printf("Error running pivot_root - %s\n", err)
os.Exit(1)
}
defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")
cmd := exec.Command(os.Args[2], os.Args[3:]...)
cmd.Env = []string{"PATH=/bin:/sbin:"}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println("ERROR", err)
os.Exit(1)
}
}
其中做几个必要的解释,在这之前有些前置工作已经完成。
alpine精简操作系统下载
本文在上述文件的同级目录下创建了一个rootfs文件,rootfs文件就是从alpine官网下载的alpine-minirootfs-3.12.0压缩包,并将其加压到rootfs文件下;
[root@node204 wuzh]# cd rootfs/
[root@node204 rootfs]# ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
隔离网络
在上文docker依赖的技术探索一文中,在测试网络隔离的时候,我们创建了一个test_ns的隔离网络并配置好了相关参数,故本文继续使用这个已经创建好的隔离网络。所以在代码中的隔离网络的路径写死为/var/run/netns/test_ns,当然大家有兴趣可以编写一个动态创建与删除的隔离网路。
运行调试
[root@node204 wuzh]# go run t_p_n.go run /bin/sh
go: finding golang.org/x/sys latest
parent run [/tmp/go-build538007245/b001/exe/t_p_n run /bin/sh]
child run [/proc/self/exe child /bin/sh]
rootfs /app/wuzh/rootfs
PivotRoot done
/ # ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 /proc/self/exe child /bin/sh
7 root 0:00 /bin/sh
9 root 0:00 ps -ef
/ # ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:24 errors:0 dropped:0 overruns:0 frame:0
TX packets:24 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:1972 (1.9 KiB) TX bytes:1972 (1.9 KiB)
veth1 Link encap:Ethernet HWaddr 66:A6:BC:ED:5D:CA
inet addr:10.1.1.1 Bcast:10.1.1.255 Mask:255.255.255.0
inet6 addr: fe80::64a6:bcff:feed:5dca/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:41 errors:0 dropped:0 overruns:0 frame:0
TX packets:41 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:3434 (3.3 KiB) TX bytes:3434 (3.3 KiB)
/ # ping 10.1.1.2
PING 10.1.1.2 (10.1.1.2): 56 data bytes
64 bytes from 10.1.1.2: seq=0 ttl=64 time=0.276 ms
64 bytes from 10.1.1.2: seq=1 ttl=64 time=0.135 ms
^C
--- 10.1.1.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.135/0.205/0.276 ms
/ # hostname
node204.ceshi.com
整个运行的过程可以看出网络的隔离,PID的隔离都基本上已经完成了,不过本文并通过cgroup来进行资源的限制。
本文的文件系统也都是简单的通过pivot_root来进行挂载与使用,当从新建的隔离进程中退出时,增删的文件都会保留在rootfs这个文件夹下面。
总结
本文只是简单的实践了一下隔离的方式,期间查阅了许多资料,动手实践的目的只是为了加深对网络隔离相关内容的理解,有关实现过程中的比如mount或者pivot_root的问题,如下博文中或者官网说明都比较清晰。
Docker 背后的内核知识——Namespace 资源隔离