借助gocker了解docker

说到docker大家都很熟悉了,Docker 是一种开源的平台和工具,用于在容器中自动化应用程序的部署、扩展和管理。但是docker具体的实现原理是怎么样的,网上有很多这方面的介绍和文章,这里主要结合gocker和namespace,cgroup,rootfs,overlay/overlay2来简单说明docker的原理,仅供个人学习和记录。


要了解docker的运行机制,需要先了解如下几个知识点

  • namespace
  • cgroup
  • UnionFS(overlay/overlay2)
  • rootfs
  • chroot

namespece

命名空间将全局系统资源包装在一个抽象中,使命名空间内的进程看起来它们拥有自己独立的全局资源实例。命名空间内对全局资源的改变对其他进程可见,命名空间的成员对其他进程不可见。

linux的7中命名空间

objectivec 复制代码
Namespace   Flag(API操作类型别名)    Isolates(隔离内容)
---------------------------------------------------------------------------------------------
Cgroup      CLONE_NEWCGROUP   Cgroup root directory (since Linux 4.6)
IPC         CLONE_NEWIPC      System V IPC, POSIX message queues (since Linux 2.6.19)
Network     CLONE_NEWNET      Network devices, stacks, ports, etc. (since Linux 2.6.24)
Mount       CLONE_NEWNS       Mount points (since Linux 2.4.19)
PID         CLONE_NEWPID      Process IDs (since Linux 2.6.24)
User        CLONE_NEWUSER     User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTS         CLONE_NEWUTS      Hostname and NIS domain name (since Linux 2.6.19)

测试demo

以pid namespace为例,实验一下

unshare --fork --pid --mount-proc /bin/bash

ini 复制代码
[root@r210 work]# unshare --fork --pid --mount-proc /bin/bash
[root@r210 work]# ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.3  0.0 117232  3588 pts/0    S    10:41   0:00 /bin/bash
root        38  0.0  0.0 157548  1912 pts/0    R+   10:41   0:00 ps -aux
[root@r210 work]# ls -l /proc/1/ns/
总用量 0
lrwxrwxrwx 1 root root 0 11月 27 10:41 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 11月 27 10:41 mnt -> mnt:[4026533144]
lrwxrwxrwx 1 root root 0 11月 27 10:41 net -> net:[4026531962]
lrwxrwxrwx 1 root root 0 11月 27 10:41 pid -> pid:[4026533145]
lrwxrwxrwx 1 root root 0 11月 27 10:41 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 11月 27 10:41 uts -> uts:[4026531838]

在新的PID Namespace中我们只能看到自身命名空间的进程。并且当前的bash处于起来的pid命名空间。

Cgroup

namespace可以是进程之间相互隔离,Cgroup最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。

测试demo

  • 创建一个cgroup控制组test
sql 复制代码
mkdir /sys/fs/cgroup/cpu/test

[root@r210 test]# ls -l /sys/fs/cgroup/cpu/test
总用量 0
-rw-r--r-- 1 root root 0 11月 25 21:55 cgroup.clone_children
--w--w--w- 1 root root 0 11月 25 21:55 cgroup.event_control
-rw-r--r-- 1 root root 0 11月 25 21:55 cgroup.procs
-r--r--r-- 1 root root 0 11月 25 21:55 cpuacct.stat
-rw-r--r-- 1 root root 0 11月 25 21:55 cpuacct.usage
-r--r--r-- 1 root root 0 11月 25 21:55 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 11月 25 21:55 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 11月 25 21:57 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 11月 25 21:55 cpu.rt_period_us
-rw-r--r-- 1 root root 0 11月 25 21:55 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 11月 25 21:55 cpu.shares
-r--r--r-- 1 root root 0 11月 25 21:55 cpu.stat
-rw-r--r-- 1 root root 0 11月 25 21:55 notify_on_release
-rw-r--r-- 1 root root 0 11月 25 21:55 tasks
  • 为了限制进程的的cpu使用率为50%,我们需要更新cpu.cfs_quota_us的值为50000
ruby 复制代码
echo 50000 >/sys/fs/cgroup/cpu/test/cpu.cfs_quota_us
  • 新建测试脚本
bash 复制代码
#!/bash/sh
while [ 1 ]; do
echo "test"
done
arduino 复制代码
[root@r210 work]# ps -ef|grep loop.sh
root       112     1 99 11:10 pts/0    00:00:51 bash loop.sh
  • 查看此时的cpu(100%)

添加图片注释,不超过 140 字(可选)

  • 将脚本PID更新到loop控制组下的tasks
ruby 复制代码
echo 112 > /sys/fs/cgroup/cpu/test/tasks
[root@r210 work]# cat /sys/fs/cgroup/cpu/test/tasks
112
  • 查看此时的cpu(50%)

添加图片注释,不超过 140 字(可选)

UnionFS

UnionFS是dokcer制作镜像以及docker容器文件读写模型的主要依赖技术。UnionFS 的主要作用是:将多个path挂载到同一个挂载点上,实现多个path的合并操作(上层同名文件会将下层的文件覆盖),最后通过挂载点向上层应用(或者用户)呈现一个path合并之后的视图。

联合文件系统有多种实现,比如Ubuntu系统的union FS实现为AUFS,centos则使用overlay/overlay2。我们以overlay文件系统为例子来说明。

overlay文件路径分为三类:

  • lowerdir 作为最底层,里面的文件是只读的,lowerdir也是分层的,多个路径用:分隔,放在后面的层级越低
  • upperdir 在lowerdir上层,可读写
  • merged 是lowerdir和upperdir合并之后暴露给用户的视图

添加图片注释,不超过 140 字(可选)

通常我们使用docker inspect 命令查看镜像的时候就可以看到这些信息

json 复制代码
"GraphDriver": {
    "Data": {
        "LowerDir": "/var/lib/docker/overlay2/d21w9t9bhu8iyu5fx8qbsm3or/diff:/var/lib/docker/overlay2/9x28sd9quqfzc0gfsr1iq08w0/diff:/var/lib/docker/overlay2/gt3hvjp2plxm2n94ya8q96thk/diff:/var/lib/docker/overlay2/5ea43db561d1b00a708e65fb2a315af53eb0c4652a5246cac44638d8d120783a/diff:/var/lib/docker/overlay2/c5754d90191186c09f15bd06e5cc55808c1f26b609b909ac51e4f95293fdd412/diff:/var/lib/docker/overlay2/4e35eb320a9ea06061e0352786879a441f8e257c0a1ec7c6b8ec13cfd170f361/diff:/var/lib/docker/overlay2/335792e0623de0a3b0dddd167edb521c7c129e356dc7c57837014865e79dcde5/diff",
        "MergedDir": "/var/lib/docker/overlay2/tr63ciarw6niwd4i3kcg5tsvl/merged",
        "UpperDir": "/var/lib/docker/overlay2/tr63ciarw6niwd4i3kcg5tsvl/diff",
        "WorkDir": "/var/lib/docker/overlay2/tr63ciarw6niwd4i3kcg5tsvl/work"
    },
    "Name": "overlay2"
},
  • 新建测试目录
csharp 复制代码
[root@r210 overlaytest]# tree .
.
├── lower
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
├── merged
├── upper
└── work
  • 挂载overlay
bash 复制代码
mount -t overlay overlay \
-o lowerdir=overlaytest/lower \
-o upperdir=overlaytest/upper \
-o workdir=overlaytest/work \
overlaytest/merged

mount -t overlay overlay: 这是挂载命令,指定要挂载的文件系统类型为 overlay。
-o lowerdir=overlaytest/lower : 这里设置了 lowerdir(底层目录),指定了底层文件系统的路径。
      这是一个存储基础文件系统内容的目录。
-o upperdir=overlaytest/upper : 这里设置了 upperdir(上层目录),指定了上层文件系统的路径。
      这是一个存储 overlay 文件系统的更改的目录。
-o workdir=overlaytest/work: 这里设置了 workdir(工作目录),指定了工作目录的路径。
      工作目录用于存储 overlay 文件系统内部的临时数据。
overlaytest/merged: 这是挂载点,也就是 overlay 文件系统最终合并后的虚拟文件系统的根目录。
      在这个路径下,你将能够看到底层文件系统和上层文件系统的合并结果。
      综合起来,这个命令的作用是将一个 overlay 文件系统挂载到指定的目录(overlaytest/merged),
      该文件系统由一个底层目录(overlaytest/lower)和一个上层目录(overlaytest/upper)组成。
      工作目录(overlaytest/work)用于存储 overlay 文件系统的内部工作数据。
      这种 overlay 文件系统的使用场景通常包括容器化技术,其中每个容器都可以有自己的上层文件系统,
      而共享同一个底层文件系统。
  • 挂载结果
bash 复制代码
df -h
overlay                  827G   37G  791G    5% /home/xxx/overlaytest/merged
  • 挂载后的文件目录结构:
csharp 复制代码
[root@r210 overlaytest]# tree .
.
├── lower
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
├── merged
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
├── upper
└── work
    └── work
  • 修改merge目录下file1.txt文件,新增file4.txt
lua 复制代码
[root@r210 overlaytest]# tree .
.
├── lower
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
├── merged
│   ├── file1.txt
│   ├── file2.txt
│   ├── file3.txt
│   └── file4.txt
├── upper
│   ├── file1.txt merged中修改file1.txt upper中也会出现该文件
│   └── file4.txt merged中新增file4.txt upper中也会出现该文件
└── work
    └── work

rootfs

根文件系统一般也叫做 rootfs,那么什么叫根文件系统?看到"文件系统"这四个字,很多人,包括我第一反应就是 FATFS、 FAT、 EXT4、 YAFFS 和 NTFS 等这样的文件系统。在这里,根文件系统并不是 FATFS 这样的文件系统代码, EXT4 这样的文件系统代码属于 Linux 内核的一部分。 Linux 中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只不过是特殊的文件夹),在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。以后我们说到文件系统,如果不特别指明,统一表示根文件系统。对于根文件系统专业的解释,百度百科上是这么说的。

根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

chroot

chroot - change root, 主要是将程序运行环境切换到指定目录。什么是将运行环境切换到指定目录呢,就是切换过去之后,在应用程序内,根目录"/"的位置变成了你指定的目录了,这样一来,就要求你的目录下必须包括程序的所有依赖环境,比如程序调用的系统命令(及其依赖项),系统调用的动态链接库等。

测试demo

bash 复制代码
# 新建路径
mkdir -p ~/chroot-test/{bin,lib,lib64,var,etc}

# 拷贝必要的文件到路径下
list=`ldd /bin/ls | egrep -o '/lib.*.[0-9]'`
for i in $list; do sudo cp -vf $i ~/chroot-test/$i; done

list=`ldd /bin/bash | egrep -o '/lib.*.[0-9]'`
for i in $list; do sudo cp $i -vf ~/chroot-test/$i; done

最终创建的目录如下:
[root@r210 chroot-test]# tree .
.
├── bin
│   ├── bash
│   ├── ls
│   ├── pwd
│   ├── sh
│   └── ssh
├── etc
├── lib
├── lib64
│   ├── ld-linux-x86-64.so.2
│   ├── libacl.so.1
│   ├── libattr.so.1
│   ├── libcap.so.2
│   ├── libc.so.6
│   ├── libdl.so.2
│   ├── libpcre.so.1
│   ├── libpthread.so.0
│   ├── libselinux.so.1
│   └── libtinfo.so.5
└── var
 
 # 启动一个 shell,并将其囚禁到~/chroot-test下
 chroot ~/chroot-test /bin/bash
 
 # 查看当前 bash进程的根路径文件
[root@worker1 ~]# chroot ~/chroot-test /bin/bash

# 看到根目录下就是刚才新建的目录:bin  etc  lib  lib64  var
bash-4.2# bin/ls 
bin  etc  lib  lib64  var
bash-4.2# bin/pwd
/

了解完这些知识点,我们再来看gocker就很轻松了

gocker

先了解下gocker是什么。Gocker是一个用 Go 语言从头实现 Docker 核心功能的项目。主要目的是让你了解容器在 Linux 系统调用级别上是如何工作的。Gocker 允许你创建容器,管理容器镜像,执行现有容器中的进程等等。gocker 提供了gocker run,gocker ps,gocker exec,gocker images,gocker rmi等类似于docker的常用命令。

我们下载gocker的源码,由于我的机器是windows的,gocker里使用到的一些函数找不到,所以通过dlv远程调试的。

安装dlv

go install github.com/go-delve/de...

goland导入项目

添加图片注释,不超过 140 字(可选)

配置远程调试

添加图片注释,不超过 140 字(可选)

远程主机启动gocker

ini 复制代码
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient \
--check-go-version=false exec gocker run nginx:latest bash

添加图片注释,不超过 140 字(可选)

本地就可以debug了

添加图片注释,不超过 140 字(可选)

gocker运行容器的过程

scss 复制代码
containerID := createContainerID()
log.Printf("New container ID: %s\n", containerID)
imageShaHex := downloadImageIfRequired(src)
log.Printf("Image to overlay mount: %s\n", imageShaHex)
createContainerDirectories(containerID)
mountOverlayFileSystem(containerID, imageShaHex)
if err := setupVirtualEthOnHost(containerID); err != nil {
	log.Fatalf("Unable to setup Veth0 on host: %v", err)
}
prepareAndExecuteContainer(mem, swap, pids, cpus, containerID, imageShaHex, args)
log.Printf("Container done.\n")
unmountNetworkNamespace(containerID)
unmountContainerFs(containerID)
removeCGroups(containerID)
os.RemoveAll(getGockerContainersPath() + "/" + containerID)

下载镜像

go 复制代码
func downloadImageIfRequired(src string) string {
	imgName, tagName := getImageNameAndTag(src) /* 获取参数中的镜像名和tag */
	if downloadRequired, imageShaHex := imageExistByTag(imgName, tagName); !downloadRequired {
		/* 使用crane.Pull下载镜像 */
		log.Printf("Downloading metadata for %s:%s, please wait...", imgName, tagName)
		img, err := crane.Pull(strings.Join([]string{imgName, tagName}, ":"))
		if err != nil {
			log.Fatal(err)
		}

		manifest, _ := img.Manifest()
		imageShaHex = manifest.Config.Digest.Hex[:12]
		log.Printf("imageHash: %v\n", imageShaHex)
		log.Println("Checking if image exists under another name...")
		/* Identify cases where ubuntu:latest could be the same as ubuntu:20.04*/
		altImgName, altImgTag := imageExistsByHash(imageShaHex)
		if len(altImgName) > 0 && len(altImgTag) > 0 {
			log.Printf("The image you requested %s:%s is the same as %s:%s\n",
				imgName, tagName, altImgName, altImgTag)
			storeImageMetadata(imgName, tagName, imageShaHex)
			return imageShaHex
		} else {
			log.Println("Image doesn't exist. Downloading...")
			downloadImage(img, imageShaHex, src)
			untarFile(imageShaHex)
			processLayerTarballs(imageShaHex, manifest.Config.Digest.Hex)
			storeImageMetadata(imgName, tagName, imageShaHex)
			deleteTempImageFiles(imageShaHex)
			return imageShaHex
		}
	} else {
		log.Println("Image already exists. Not downloading.")
		return imageShaHex
	}
}

下载的镜像存放在/var/lib/gocker/images目录下

diff 复制代码
drwxr-xr-x 9 root root 192 11月 24 17:21 a6bd71f48f68
-rw-r--r-- 1 root root  35 11月 24 17:21 images.json

创建容器目录

go 复制代码
func createContainerDirectories(containerID string) {
	contHome := getGockerContainersPath() + "/" + containerID
	contDirs := []string{contHome + "/fs", contHome + "/fs/mnt", contHome + "/fs/upperdir", contHome + "/fs/workdir"}
	if err := createDirsIfDontExist(contDirs); err != nil {
		log.Fatalf("Unable to create required directories: %v\n", err)
	}
}

添加图片注释,不超过 140 字(可选)

挂载overlay文件系统

go 复制代码
func mountOverlayFileSystem(containerID string, imageShaHex string) {
	var srcLayers []string
	pathManifest := getManifestPathForImage(imageShaHex)
	mani := manifest{}
	parseManifest(pathManifest, &mani)
	if len(mani) == 0 || len(mani[0].Layers) == 0 {
		log.Fatal("Could not find any layers.")
	}
	if len(mani) > 1 {
		log.Fatal("I don't know how to handle more than one manifest.")
	}

	imageBasePath := getBasePathForImage(imageShaHex)
	for _, layer := range mani[0].Layers {
		srcLayers = append([]string{imageBasePath + "/" + layer[:12] + "/fs"}, srcLayers...)
		//srcLayers = append(srcLayers, imageBasePath + "/" + layer[:12] + "/fs")
	}
	contFSHome := getContainerFSHome(containerID)
	mntOptions := "lowerdir=" + strings.Join(srcLayers, ":") + ",upperdir=" + contFSHome + "/upperdir,workdir=" + contFSHome + "/workdir"
	if err := unix.Mount("none", contFSHome+"/mnt", "overlay", 0, mntOptions); err != nil {
		log.Fatalf("Mount failed: %v\n", err)
	}
}

添加图片注释,不超过 140 字(可选)

这里看到merged目录就是:/var/run/gocker/containers/5821dd72827f/fs/mnt,lowerdir就是刚才下载镜像的目录

挂载成功后看到文件生成了

添加图片注释,不超过 140 字(可选)

df -h

bash 复制代码
none                      16G  322M   16G    3% /run/gocker/containers/5821dd72827f/fs/mnt

启动容器并设置新的命名空间

ini 复制代码
func prepareAndExecuteContainer(mem int, swap int, pids int, cpus float64,
	containerID string, imageShaHex string, cmdArgs []string) {

.. ... ...
opts = append(opts, "--img="+imageShaHex)
	args := append([]string{containerID}, cmdArgs...)
	args = append(opts, args...)
	args = append([]string{"child-mode"}, args...)
	cmd = exec.Command("/proc/self/exe", args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.SysProcAttr = &unix.SysProcAttr{
		Cloneflags: unix.CLONE_NEWPID |
			unix.CLONE_NEWNS |
			unix.CLONE_NEWUTS |
			unix.CLONE_NEWIPC,
	}
	doOrDie(cmd.Run())
}

chroot指定刚才挂载的overlay文件系统

go 复制代码
func execContainerCommand(mem int, swap int, pids int, cpus float64,
	containerID string, imageShaHex string, args []string) {
	mntPath := getContainerFSHome(containerID) + "/mnt"
	cmd := exec.Command(args[0], args[1:]...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	imgConfig := parseContainerConfig(imageShaHex)
	doOrDieWithMsg(unix.Sethostname([]byte(containerID)), "Unable to set hostname")
	doOrDieWithMsg(joinContainerNetworkNamespace(containerID), "Unable to join container network namespace")
	createCGroups(containerID, true)
	configureCGroups(containerID, mem, swap, pids, cpus)
	doOrDieWithMsg(copyNameserverConfig(containerID), "Unable to copy resolve.conf")
	doOrDieWithMsg(unix.Chroot(mntPath), "Unable to chroot")
	doOrDieWithMsg(os.Chdir("/"), "Unable to change directory")
	createDirsIfDontExist([]string{"/proc", "/sys"})
	doOrDieWithMsg(unix.Mount("proc", "/proc", "proc", 0, ""), "Unable to mount proc")
	doOrDieWithMsg(unix.Mount("tmpfs", "/tmp", "tmpfs", 0, ""), "Unable to mount tmpfs")
	doOrDieWithMsg(unix.Mount("tmpfs", "/dev", "tmpfs", 0, ""), "Unable to mount tmpfs on /dev")
	createDirsIfDontExist([]string{"/dev/pts"})
	doOrDieWithMsg(unix.Mount("devpts", "/dev/pts", "devpts", 0, ""), "Unable to mount devpts")
	doOrDieWithMsg(unix.Mount("sysfs", "/sys", "sysfs", 0, ""), "Unable to mount sysfs")
	setupLocalInterface()
	cmd.Env = imgConfig.Config.Env
	cmd.Run()
	doOrDie(unix.Unmount("/dev/pts", 0))
	doOrDie(unix.Unmount("/dev", 0))
	doOrDie(unix.Unmount("/sys", 0))
	doOrDie(unix.Unmount("/proc", 0))
	doOrDie(unix.Unmount("/tmp", 0))
}

关键代码:

less 复制代码
doOrDieWithMsg(unix.Sethostname([]byte(containerID)),
doOrDieWithMsg(unix.Chroot(mntPath), "Unable to chroot")
... ... ...
cmd.Run()

这步执行完成,就进入到容器目录了

添加图片注释,不超过 140 字(可选)

容器退出,清理现场

scss 复制代码
	unmountNetworkNamespace(containerID)
	unmountContainerFs(containerID)
	removeCGroups(containerID)
	os.RemoveAll(getGockerContainersPath() + "/" + containerID)

执行完这些代码后,mount和/var/run/gocker/containers目录就恢复原状了

到此处其实还有一处没有说明,就是容器网络是在哪设置的,下面我们一起看看

创建一对veth

veth0_xxxxx, veth1_xxxxx,然后把veth0绑定到gocker0网桥上

go 复制代码
func setupVirtualEthOnHost(containerID string) error {
	veth0 := "veth0_" + containerID[:6]
	veth1 := "veth1_" + containerID[:6]
	linkAttrs := netlink.NewLinkAttrs()
	linkAttrs.Name = veth0
	veth0Struct := &netlink.Veth{
		LinkAttrs:        linkAttrs,
		PeerName:         veth1,
		PeerHardwareAddr: createMACAddress(),
	}
	if err := netlink.LinkAdd(veth0Struct); err != nil {
		return err
	}
	netlink.LinkSetUp(veth0Struct) 
	gockerBridge, _ := netlink.LinkByName("gocker0")
	netlink.LinkSetMaster(veth0Struct, gockerBridge)

	return nil
}

添加图片注释,不超过 140 字(可选)

将veth1_xxxxx移动新的命名空间中

go 复制代码
func setupContainerNetworkInterfaceStep1(containerID string) {
	nsMount := getGockerNetNsPath() + "/" + containerID

	fd, err := unix.Open(nsMount, unix.O_RDONLY, 0)
	defer unix.Close(fd)
	if err != nil {
		log.Fatalf("Unable to open: %v\n", err)
	}
	/* Set veth1 of the new container to the new network namespace */
	veth1 := "veth1_" + containerID[:6]
	veth1Link, err := netlink.LinkByName(veth1)
	if err != nil {
		log.Fatalf("Unable to fetch veth1: %v\n", err)
	}
	if err := netlink.LinkSetNsFd(veth1Link, fd); err != nil {
		log.Fatalf("Unable to set network namespace for veth1: %v\n", err)
	}
}

给veth1_xxxxx设置ip,并在容器中增加路由

go 复制代码
func setupContainerNetworkInterfaceStep2(containerID string) {
	nsMount := getGockerNetNsPath() + "/" + containerID
	fd, err := unix.Open(nsMount, unix.O_RDONLY, 0)
	defer unix.Close(fd)
	if err != nil {
		log.Fatalf("Unable to open: %v\n", err)
	}
	if err := unix.Setns(fd, unix.CLONE_NEWNET); err != nil {
		log.Fatalf("Setns system call failed: %v\n", err)
	}

	veth1 := "veth1_" + containerID[:6]
	veth1Link, err := netlink.LinkByName(veth1)
	if err != nil {
		log.Fatalf("Unable to fetch veth1: %v\n", err)
	}
	addr, _ := netlink.ParseAddr(createIPAddress() + "/16")
	if err := netlink.AddrAdd(veth1Link, addr); err != nil {
		log.Fatalf("Error assigning IP to veth1: %v\n", err)
	}

	/* Bring up the interface */
	doOrDieWithMsg(netlink.LinkSetUp(veth1Link), "Unable to bring up veth1")

	/* Add a default route */
	route := netlink.Route{
		Scope:     netlink.SCOPE_UNIVERSE,
		LinkIndex: veth1Link.Attrs().Index,
		Gw:        net.ParseIP("172.29.0.1"),
		Dst:       nil,
	}
	doOrDieWithMsg(netlink.RouteAdd(&route), "Unable to add default route")
}

添加图片注释,不超过 140 字(可选)

这些设置完成后容器和宿主机的网络就通了,但是容器如果要访问外网还是不行的

添加图片注释,不超过 140 字(可选)

需要设置nat转发

css 复制代码
iptables -t nat -A POSTROUTING -s 172.29.0.0/16 -j MASQUERADE

添加图片注释,不超过 140 字(可选)

参考资料:

polarisxu:容器的艰难之旅:gocker ------ Go 实现的迷你 Docker

开放云:docker容器技术基础之linux cgroup、namespace

理解docker基础-文件系统隔离 - 掘金

程序员小x:使用veth和bridge模拟容器网络

相关推荐
自在的LEE5 小时前
当 Go 遇上 Windows:15.625ms 的时间更新困局
后端·kubernetes·go
Gvto1 天前
使用FakeSMTP创建本地SMTP服务器接收邮件具体实现。
go·smtp·mailtrap
白泽来了2 天前
【Go进阶】手写 Go websocket 库(一)|WebSocket 通信协议
开源·go
witton2 天前
将VSCode配置成Goland的视觉效果
ide·vscode·编辑器·go·字体·c/c++·goland
非凡的世界2 天前
5个用于构建Web应用程序的Go Web框架
golang·go·框架·web
湫qiu2 天前
6.5840 Lab-Key/Value Server 思路
后端·go
我是前端小学生2 天前
Go语言中的init函数
go
我是前端小学生3 天前
Go语言中内部模块的可见性规则
go
我是前端小学生3 天前
一文理解Go Modules的相关内容
go
非凡的世界3 天前
Iris简单实现Go web服务器
golang·go