简单编写一个容器

背景

经过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的问题,如下博文中或者官网说明都比较清晰。

动手实现一个容器

wwcdocker编写的容器

容器虚拟化隔离方式的解析与简单实现

Golang+shell快速实现docker运行时

Docker 背后的内核知识——Namespace 资源隔离

Go语言实现容器namespace和cgroups

Containers From Scratch with Golang

一步步打造一个迷你linux container

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值