说到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
- nohup bash loop.sh &
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