Docker exec的实现原理是什么
Docker exec的实现原理是什么
本篇内容主要讲解“Dockerexec的实现原理是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Dockerexec的实现原理是什么”吧!
我使用了 docker exec
命令进入到了容器当中。在了解了Linux Namespace
的隔离机制后,你应该会很自然地想到一个问题:docker exec 是怎么做到进入容器里的呢?
实际上,Linux Namespace
创建的隔离空间虽然看不见摸不着,但一个进程的 Namespace
信息在宿主机上是确确实实存在的,并且是以一个文件的方式存在。
比如,通过如下指令,你可以看到当前正在运行的 Docker 容器的进程号(PID)是 25686:
$dockerinspect--format'{{.State.Pid}}'4ddf4638572d25686
这时,你可以通过查看宿主机的 proc 文件,看到这个 25686 进程的所有 Namespace 对应的文件:
$ls-l/proc/25686/nstotal0lrwxrwxrwx1rootroot0Aug1314:05cgroup->cgroup:[4026531835]lrwxrwxrwx1rootroot0Aug1314:05ipc->ipc:[4026532278]lrwxrwxrwx1rootroot0Aug1314:05mnt->mnt:[4026532276]lrwxrwxrwx1rootroot0Aug1314:05net->net:[4026532281]lrwxrwxrwx1rootroot0Aug1314:05pid->pid:[4026532279]lrwxrwxrwx1rootroot0Aug1314:05pid_for_children->pid:[4026532279]lrwxrwxrwx1rootroot0Aug1314:05user->user:[4026531837]lrwxrwxrwx1rootroot0Aug1314:05uts->uts:[4026532277]
可以看到,一个进程的每种Linux Namespace
,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上。
有了这样一个可以“hold 住”所有 Linux Namespace 的文件,我们就可以对 Namespace 做一些很有意义事情了,比如:加入到一个已经存在的 Namespace 当中。
这也就意味着:一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。
而这个操作所依赖的,乃是一个名叫 setns() 的 Linux 系统调用。它的调用方法,我可以用如下一段小程序为你说明:
#define_GNU_SOURCE#include<fcntl.h>#include<sched.h>#include<unistd.h>#include<stdlib.h>#include<stdio.h>#defineerrExit(msg)do{perror(msg);exit(EXIT_FAILURE);}while(0)intmain(intargc,char*argv[]){intfd;fd=open(argv[1],O_RDONLY);if(setns(fd,0)==-1){errExit("setns");}execvp(argv[2],&argv[2]);errExit("execvp");}
这段代码功能非常简单:它一共接收两个参数,第一个参数是 argv[1],即当前进程要加入的 Namespace 文件的路径,比如/proc/25686/ns/net
;而第二个参数,则是你要在这个 Namespace
里运行的进程,比如 /bin/bash。
这段代码的的核心操作,则是通过 open() 系统调用打开了指定的 Namespace
文件,并把这个文件的描述符 fd 交给 setns() 使用。在 setns() 执行后,当前进程就加入了这个文件对应的 Linux Namespace
当中了。
现在,你可以编译执行一下这个程序,加入到容器进程(PID=25686)的 Network Namespace 中:
$gcc-oset_nsset_ns.c$./set_ns/proc/25686/ns/net/bin/bash$ifconfigeth0Linkencap:EthernetHWaddr02:42:ac:11:00:02inetaddr:172.17.0.2Bcast:0.0.0.0Mask:255.255.0.0inet6addr:fe80::42:acff:fe11:2/64Scope:LinkUPBROADCASTRUNNINGMULTICASTMTU:1500Metric:1RXpackets:12errors:0dropped:0overruns:0frame:0TXpackets:10errors:0dropped:0overruns:0carrier:0collisions:0txqueuelen:0RXbytes:976(976.0B)TXbytes:796(796.0B)loLinkencap:LocalLoopbackinetaddr:127.0.0.1Mask:255.0.0.0inet6addr:::1/128Scope:HostUPLOOPBACKRUNNINGMTU:65536Metric:1RXpackets:0errors:0dropped:0overruns:0frame:0TXpackets:0errors:0dropped:0overruns:0carrier:0collisions:0txqueuelen:1000RXbytes:0(0.0B)TXbytes:0(0.0B)
正如上所示,当我们执行 ifconfig
命令查看网络设备时,我会发现能看到的网卡“变少”了:只有两个。而我的宿主机则至少有四个网卡。这是怎么回事呢?
实际上,在 setns() 之后我看到的这两个网卡,正是我在前面启动的 Docker 容器里的网卡。也就是说,我新创建的这个 /bin/bash 进程,由于加入了该容器进程(PID=25686)的 Network Namepace,它看到的网络设备与这个容器里是一样的,即:/bin/bash 进程的网络设备视图,也被修改了。
而一旦一个进程加入到了另一个 Namespace 当中,在宿主机的 Namespace 文件上,也会有所体现。
在宿主机上,你可以用 ps 指令找到这个 set_ns 程序执行的 /bin/bash 进程,其真实的 PID 是 28499:
#在宿主机上psaux|grep/bin/bashroot284990.00.0199443612pts/0S14:150:00/bin/bash
这时,如果按照前面介绍过的方法,查看一下这个 PID=28499 的进程的 Namespace,你就会发现这样一个事实:
$ls-l/proc/28499/ns/netlrwxrwxrwx1rootroot0Aug1314:18/proc/28499/ns/net->net:[4026532281]$ls-l/proc/25686/ns/netlrwxrwxrwx1rootroot0Aug1314:05/proc/25686/ns/net->net:[4026532281]
在 /proc/[PID]/ns/net
目录下,这个 PID=28499 进程,与我们前面的 Docker 容器进程(PID=25686)指向的 Network Namespace
文件完全一样。这说明这两个进程,共享了这个名叫net:[4026532281] 的 Network
Namespace。
此外,Docker 还专门提供了一个参数,可以让你启动一个容器并“加入”到另一个容器的 Network Namespace 里,这个参数就是 -net,比如:
$dockerrun-it--netcontainer:4ddf4638572dbusyboxifconfig
这样,我们新启动的这个容器,就会直接加入到 ID=4ddf4638572d
的容器,也就是我们前面的创建的应用容器(PID=25686)的Network Namespace
中。所以,这里 ifconfig 返回的网卡信息,跟我前面那个小程序返回的结果一模一样,你也可以尝试一下。
而如果我指定–net=host,就意味着这个容器不会为进程启用 Network Namespace
。这就意味着,这个容器拆除了 Network Namespace
的“隔离墙”,所以,它会和宿主机上的其他普通进程一样,直接共享宿主机的网络栈。这就为容器直接操作和使用宿主机网络提供了一个渠道。
到此,相信大家对“Dockerexec的实现原理是什么”有了更深的了解,不妨来实际操作一番吧!这里是恰卡编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!