你的留言,是我坚持分享的动力。如果你有任何疑问,可以点击关注,点赞、留言提问。 云计算领域最火的莫过于容器,而提到容器,就不得不提Docker,可以说Docker已经是容器的代名词。 容器其实是一种沙盒技术,顾名思义,沙盒就是能够像集装箱一样,把应用装起来的技术。这样,应用和应用之间就有了边界,不互相干扰。 而我们通常会把容器技术和虚拟化技术做对比,应该会常常看到这样一张图 左边的图,画出了虚拟机的工作原理。其中,Hypervisor是虚拟机的重要组成部分,通过硬件虚拟化功能,模拟出了运行一个操作系统需要的各种硬件,比如CPU、内存、IO设备等,然后,它在这些虚拟的硬件上安装了一个新的操作系统,即GuestOS。 而容器是进程级隔离,依靠Namespace机制实现进程间隔离,Cgroups实现进程资源限制。一、LinuxNamespace LinuxNamespace是Kernel的一个功能,可以隔离一系列的系统资源,比如PID(ProcessID)、UserID、Network等。命名空间建立系统不同的视图,从用户的角度来看,每个命名空间就像是一台独立的Linux计算机一样,有自己的init进程(PID为1)。 当前Linux一共实现了6种不同类型的Namespace: Namespace类型 系统调用参数 内核版本 Mount CLONENEWNS 2。4。19 UTS CLONENEWUTS 2。6。19 IPC CLONENEWIPC 2。6。19 PID CLONENEWPID 2。6。24 Network CLONENEWNET 2。6。29 User CLONENEWUSER 3。8 Namespace的API主要使用如下3个系统调用:clone()创建新进程。根据系统调用的参数判断哪些类型的Namespace被创建,而它的子进程也会被添加到这些Namespace中unshare()将进程移除某个Namespacesetns()将进程加入到Namespace中1。1、UTCNamespace UTCNamespace主要用来隔离nodename和domainname两个系统标识。在UTCNamespace中,每个Namespace允许有自己的hostname。 我们写一个UTCNamespace的例子packagemainimport(ososexecsyscall)funcmain(){cmd:exec。Command(sh)cmd。SysProcAttrsyscall。SysProcAttr{Cloneflags:syscall。CLONENEWUTS,}cmd。Stdinos。Stdincmd。Stdoutos。Stdoutcmd。Stderros。Stderrerr:cmd。Run()iferr!nil{panic(err)}} exec。Command(sh)用来指定被fork处理的新进程的初始命令,默认使用sh来执行。然后就是设置系统调用参数,用CLONENEWUTS标识符去创建一个UTCNamespace。 执行gorunmain。go,使用pstreepl查看系统中进程之间的关系。sshd(1194)sshd(22904)bash(22915)go(23058)main(23075)sh(23078){main}(23076){main}(23077){go}(23059){go}(23060){go}(23061){go}(23074){go}(23079) 输出当前的PIDecho23078 验证一下父进程和子进程是否在同一UTCNamespacereadlinkproc23075nsutsuts:〔4026531838〕readlinkproc23078nsutsuts:〔4026532284〕 可以看到它们确实不在同一个UTCNamespace中。 由于UTCNamespace对hostname做了隔离,所以在这个环境中修改hostname应该不影响外部主机。 在shell环境下执行命令hostnamebgolanghostnamegolang 在外部环境下执行命令hostnamecontainer 可以看到,外部的hostname确实没有被内部的修改影响到。1。2、IPCNamespace IPC全称为InterProcessCommunication,是UnixLinux下进程通信的一种方式,IPC有共享内存、信号量、消息队列等方法。为了隔离进程,也需要把IPCNamespace隔离开,这样,只有同一个命名空间下的进程才能够互相通信。packagemainimport(ososexecsyscall)funcmain(){cmd:exec。Command(sh)cmd。SysProcAttrsyscall。SysProcAttr{Cloneflags:syscall。CLONENEWUTSsyscall。CLONENEWIPC,}cmd。Stdinos。Stdincmd。Stdoutos。Stdoutcmd。Stderros。Stderrerr:cmd。Run()iferr!nil{panic(err)}} 我们在原先的代码仅做一点改动,增加CLONENEWIPC,希望创建IPCNamespace。 在宿主机上打开一个shell查看现有的ipcMessageQueuesipcsqMessageQueueskeymsqidownerpermsusedbytesmessages在全局创建一个ipc的队列,队列id为0ipcmkQMessagequeueid:0查看刚刚新建的全局队列的信息ipcsqMessageQueueskeymsqidownerpermsusedbytesmessages0xad7e5e0d0root64400 然后,我们再执行main。go,在sh下查看ipc队列gorunmain。goshipcsqMessageQueueskeymsqidownerpermsusedbytesmessages 可以看到,在新创建的Namespace看不到宿主机已经创建的messagequeue。同样,在新创建的Namespace里创建的队列,在全局中也无法看到。1。3、PIDNamespace PIDNamespace是用来隔离进程ID的,同一个进程,在Namespace里和在外部有着不同的进程PID。packagemainimport(ososexecsyscall)funcmain(){cmd:exec。Command(sh)cmd。SysProcAttrsyscall。SysProcAttr{Cloneflags:syscall。CLONENEWUTSsyscall。CLONENEWIPCsyscall。CLONENEWPID,}cmd。Stdinos。Stdincmd。Stdoutos。Stdoutcmd。Stderros。Stderrerr:cmd。Run()iferr!nil{panic(err)}} 添加一个CLONENEWPID,代表fork出来的子进程创建自己的PIDNamespace。 用pstreepl在宿主机上查看进程树,找到真实的PID,然后在PIDNamespace打印PID一个shell中运行gorunmain。goshecho1另一个shell中查看进程树pstreeplsshd(1194)sshd(22904)bash(22915)go(23192)main(23212)sh(23215) 可以看到在PIDNamespace打印PID为1,也就是说23212这个PID被映射为1。 这里不能用ps查看,因为ps和top等命令是使用proc的内容。1。4、MountNamespace 程序运行时可以将挂载点和系统分离,使用这个功能,我们可以达到chroot的功能。 MountNamespace用来隔离各个进程看到的挂载点视图,在不同的Namespace的进程中,看到的文件系统的层次是不一样的。 在MountNamespace中调用mount()和unmount()仅仅只会影响当前Namespace内的文件系统,对全局文件系统是不影响的。packagemainimport(ososexecsyscall)funcmain(){cmd:exec。Command(sh)cmd。SysProcAttrsyscall。SysProcAttr{Cloneflags:syscall。CLONENEWUTSsyscall。CLONENEWIPCsyscall。CLONENEWPIDsyscall。CLONENEWNS,}cmd。Stdinos。Stdincmd。Stdoutos。Stdoutcmd。Stderros。Stderrerr:cmd。Run()iferr!nil{panic(err)}} 同样增加一点小改动syscall。CLONENEWNS,然后运行代码,查看proc的文件内容。lsproc1157661604320229152345627601438050cryptokallsymsmountssys1015770160612080323234982788944828deviceskcoremtrrsysrqtrigger10451578216079208222319223527999459diskstatskeysnetsysvipc10511580016097208782321223521304694dmakeyuserspagetypeinfotimerlist1061158871611520882232152352431468955driverkmsgpartitionstimerstats1062159061612208962332352631770472957execdomainskpagecountscheddebugtty111590716185209142333923632478acpifbkpageflagsschedstatuptime1194159081720999233872433540buddyinfofilesystemsloadavgscsiversion1315910182123424135059busfslocksselfvmallocinfo14159991922234422473726cgroupsinterruptsmdstatslabinfovmstat1516193522272344425747cmdlineiomemmeminfosoftirqszoneinfo1575016012198982290023446258403760consolesioportsmiscstat15754160212229042345327588418cpuinfoirqmodulesswaps 因为这里的proc还是宿主机的,所以我们用mount到Namespace里mounttprocprocproclsproc1devicesioportslocksscheddebugsysvipc4diskstatsirqmdstatschedstattimerlistacpidmakallsymsmeminfoscsitimerstatsbuddyinfodriverkcoremiscselfttybusexecdomainskeysmodulesslabinfouptimecgroupsfbkeyusersmountssoftirqsversioncmdlinefilesystemskmsgmtrrstatvmallocinfoconsolesfskpagecountnetswapsvmstatcpuinfointerruptskpageflagspagetypeinfosyszoneinfocryptoiomemloadavgpartitionssysrqtrigger 可以看到,数字少了很多,这样就能够运行ps命令了psefUIDPIDPPIDCSTIMETTYTIMECMDroot10013:53pts100:00:00shroot51013:59pts100:00:00psef 可以看到,sh进程时PID为1的进程,当前MountNamespace的mount和外部空间是隔离的。1。5、UserNamespace UserNamespace主要是隔离用户的用户组ID,也就是说,一个进程的UserID和GroupID在命名空间内外可以是不同的。 比如,在宿主机上以一个非root用户创建的UserNamespace,在UserNamespace里被映射为root用户。packagemainimport(ososexecsyscall)funcmain(){cmd:exec。Command(sh)cmd。SysProcAttrsyscall。SysProcAttr{Cloneflags:syscall。CLONENEWUTSsyscall。CLONENEWIPCsyscall。CLONENEWPIDsyscall。CLONENEWNSsyscall。CLONENEWUSER,}cmd。Stdinos。Stdincmd。Stdoutos。Stdoutcmd。Stderros。Stderrerr:cmd。Run()iferr!nil{panic(err)}} 同样增加syscall。CLONENEWUSER。 首先以root用户运行程序查看当前用户和用户组iduid0(root)gid0(root)groups0(root) 我的系统是CentOS7,所以在执行前需要做一些工作。需要开启UserNamespacegrubbyargsusernamespace。enable1updatekernel(grubbydefaultkernel)重启rebootecho640procsysusermaxusernamespaces 之后就可以正常运行程序了gorunmain。goshiduid65534gid65534groups65534 可以看到,它们的UID不同,说明UserNamespace生效了。1。6、NetworkNamespace NetworkNamespace用于隔离网络资源(procnet、IP地址端口、网卡、路由等),让每个容器都有自己独立的(虚拟)网络设备,每个网络命名空间都有自己的路由表,iptables。packagemainimport(logososexecsyscall)funcmain(){cmd:exec。Command(sh)cmd。SysProcAttrsyscall。SysProcAttr{Cloneflags:syscall。CLONENEWUTSsyscall。CLONENEWIPCsyscall。CLONENEWPIDsyscall。CLONENEWNSsyscall。CLONENEWUSERsyscall。CLONENEWNET,}cmd。Stdinos。Stdincmd。Stdoutos。Stdoutcmd。Stderros。Stderrerr:cmd。Run()iferr!nil{panic(err)}} 首先在宿主机上查看自己的网络设备ifconfigeth0:flags4163UP,BROADCAST,RUNNING,MULTICASTmtu1500inet172。27。158。184netmask255。255。240。0broadcast172。27。159。255inet6fe80::216:3eff:fe19:d2fcprefixlen64scopeid0x20linkether00:16:3e:19:d2:fctxqueuelen1000(Ethernet)RXpackets4583bytes3471561(3。3MiB)RXerrors0dropped0overruns0frame0TXpackets2986bytes1983980(1。8MiB)TXerrors0dropped0overruns0carrier0collisions0lo:flags73UP,LOOPBACK,RUNNINGmtu65536inet127。0。0。1netmask255。0。0。0inet6::1prefixlen128scopeid0x10hostlooptxqueuelen1000(LocalLoopback)RXpackets0bytes0(0。0B)RXerrors0dropped0overruns0frame0TXpackets0bytes0(0。0B)TXerrors0dropped0overruns0carrier0collisions0 可以看到有eth0和lo网络设备,然后运行程序gorunmain。goshifconfigsh 我们发现,在NetworkNamespace里什么网络设备都没有,这样NetworkNamespace和宿主机之间网络是隔离的。二、LinuxCgoups LinuxCgoups全称是LinuxControlGroup,它的主要作用就是限制一个进程组能够使用的资源上限,包括CPU、内存、磁盘、网络等。 在Linux中,Cgroups给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在sysfscgroup路径下。mounttcgroupcgrouponsysfscgroupsystemdtypecgroup(rw,nosuid,nodev,noexec,relatime,xattr,releaseagentusrlibsystemdsystemdcgroupsagent,namesystemd)cgrouponsysfscgroupnetcls,netpriotypecgroup(rw,nosuid,nodev,noexec,relatime,netprio,netcls)cgrouponsysfscgroupmemorytypecgroup(rw,nosuid,nodev,noexec,relatime,memory)cgrouponsysfscgroupcpusettypecgroup(rw,nosuid,nodev,noexec,relatime,cpuset)cgrouponsysfscgroupperfeventtypecgroup(rw,nosuid,nodev,noexec,relatime,perfevent)cgrouponsysfscgroupfreezertypecgroup(rw,nosuid,nodev,noexec,relatime,freezer)cgrouponsysfscgrouphugetlbtypecgroup(rw,nosuid,nodev,noexec,relatime,hugetlb)cgrouponsysfscgroupblkiotypecgroup(rw,nosuid,nodev,noexec,relatime,blkio)cgrouponsysfscgroupcpu,cpuaccttypecgroup(rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)cgrouponsysfscgroupdevicestypecgroup(rw,nosuid,nodev,noexec,relatime,devices)cgrouponsysfscgrouppidstypecgroup(rw,nosuid,nodev,noexec,relatime,pids) 可以看到,在sysfscgroup下面有很多cpu、memory这样的子目录,也就称为子系统subsystem,它是一组资源控制模块,一般包含如下几项:netcls:将cgroup中进程产生的网络包分类,以便Linux的tc(trafficcontroller)可以根据分类区分出来自某个cgroup的包并做限流或监控netprio:设置cgroup中进程产生的网络流量的优先级memory:控制cgroup中进程的内存占用cpuset:在多核机器上设置cgroup中进程可以使用的cpu和内存freezer:挂起(suspend)和恢复(resume)cgroup中的进程blkio:设置对块设备(如硬盘)输入输出的访问控制cpu:设置cgroup中进程的CPU占用cpuacct:统计cgroup中进程的CPU占用devices:控制cgroup中进程对设备的访问2。1、挂载cgroup 我们创建并挂载一个cgroupmkdircgrouptestmounttcgrouponone,namecgrouptestcgrouptest。cgrouptestls。cgrouptestcgroup。clonechildrencgroup。eventcontrolcgroup。procscgroup。sanebehaviornotifyonreleasereleaseagenttasks 挂载后我们能看到系统在这个目录下生成了一些默认的文件:cgroup。clonechildren:cpuset的subsystem会读取这个配置文件,如果这个值是1(默认是0),子cgroup才会继承父cgroup的cpuset的配置cgroup。eventcontrol:监控状态变化和分组删除事件的配置文件cgroup。procs:树中当前节点cgroup的进程组IDnotifyonrelease和releaseagent:notifyonrelease标识这个cgroup最后一个进程退出时是否执行了releaseagent,releaseagent则是一个路径,通常用作进程退出后清理不再使用的cgrouptask:标识该cgroup下的进程ID,如果把一个进程ID写入tasks文件中,便会将相应的进程加入到这个cgroup中2。2、新建子cgroupmkdircgroup1mkdircgroup2lscgroup1cgroup。clonechildrencgroup。procsnotifyonreleasetaskscgroup2cgroup。eventcontrolcgroup。sanebehaviorreleaseagenttree。cgroup1cgroup。clonechildrencgroup。eventcontrolcgroup。procsnotifyonreleasetaskscgroup2cgroup。clonechildrencgroup。eventcontrolcgroup。procsnotifyonreleasetaskscgroup。clonechildrencgroup。eventcontrolcgroup。procscgroup。sanebehaviornotifyonreleasereleaseagenttasks 在挂载目录下创建文件夹时,操作系统会把文件夹标记为子cgroup,会继承父cgroup的属性2。3、添加进程 把进程ID写入到cgroup节点的tasks文件中〔cgroup1〕echo1186〔cgroup1〕shcechotasks〔cgroup1〕catproc1186cgroup12:namecgrouptest:cgroup111:pids:10:devices:9:cpuacct,cpu:8:blkio:7:hugetlb:6:freezer:5:perfevent:4:cpuset:3:memory:2:netprio,netcls:1:namesystemd:user。sliceuser0。slicesession1。scope 可以看到,当前进程1186已经被加入到cgrouptest:cgroup1中了2。4、限制资源 我们来写一个脚本while:;do:;done〔1〕1448 使用top命令查看,可以看到CPU已经占满topCpu(s):100。0us,0。0sy,0。0ni,0。0id,0。0wa,0。0hi,0。0si,0。0st 然后我们通过cgroup限制这个进程资源mountgrepcpucgrouponsysfscgroupcpusettypecgroup(rw,nosuid,nodev,noexec,relatime,cpuset)cgrouponsysfscgroupcpu,cpuaccttypecgroup(rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)cdsysfscgroupcpulsaegiscgroup。eventcontrolcpuacct。statcpu。cfsperioduscpu。rtruntimeusnotifyonreleaseassistcgroup。procscpuacct。usagecpu。cfsquotauscpu。sharesreleaseagentcgroup。clonechildrencgroup。sanebehaviorcpuacct。usagepercpucpu。rtperioduscpu。stattasks创建一个cgroupmkdirtestlimitcpucdtestlimitcpu设置cpu占用容量,意味着在每100ms时间内,该进程只能使用20ms的CPU时间echo20000cpu。cfsquotaus将当前进程添加进cgroup中echo1448tasks 通过top命令查看topCpu(s):20。5us,0。7sy,0。0ni,78。8id,0。0wa,0。0hi,0。0si,0。0st 可以看到计算机的CPU使用率立刻降到了20。2。5、Docker使用Cgroup LinuxCgroup的设计还是比较简单的,就是在子目录系统加上一组限制资源文件的组合。而对于Docker来说,只需要在每个子系统下面,为每个容器创建一个控制组(新建目录),然后在启动容器进程后,把这个进程的PID填写到对应控制组的tasks文件中即可。dockerrunitdm128mbusybox3da89b011026f40a22b22c9b9b8e15e5fb85045c6aa72f7b54406f141cd63157cdsysfscgroupmemorydocker3da89b011026f40a22b22c9b9b8e15e5fb85045c6aa72f7b54406f141cd63157catmemory。limitinbytes134217728 可以看到,Docker通过为每个容器创建cgroup,并通过cgroup去配置资源限制和资源监控。2。6、程序实现packagemainimport(fmtioioutilososexecpathstrconvsyscall)constcgroupMountPathsysfscgroupcpufuncmain(){ifos。Args〔0〕procselfexe{容器进程fmt。Printf(currentpidd,syscall。Getpid())cmd:exec。Command(sh,c,while:;do:;done)cmd。SysProcAttrsyscall。SysProcAttr{}cmd。Stdinos。Stdincmd。Stdoutos。Stdoutcmd。Stderros。Stderrerr:cmd。Run()iferr!nil{panic(err)}}cmd:exec。Command(procselfexe)cmd。SysProcAttrsyscall。SysProcAttr{Cloneflags:syscall。CLONENEWUTSsyscall。CLONENEWPIDsyscall。CLONENEWNS,}cmd。Stdinos。Stdincmd。Stdoutos。Stdoutcmd。Stderros。Stderrerr:cmd。Start()iferr!nil{panic(err)}得到fork出来进程映射在外部命名空间的pidfmt。Printf(v,cmd。Process。Pid)在系统默认创建挂载了cpusubsystem上创建cgrouppidCgroup:path。Join(cgroupMountPath,testcpulimit)os。Mkdir(pidCgroup,0775)将容器进程加入到这个cgroup中ioutil。WriteFile(path。Join(pidCgroup,tasks),〔〕byte(strconv。Itoa(cmd。Process。Pid)),0644)限制cgroup使用ioutil。WriteFile(path。Join(pidCgroup,cpu。cfsquotaus),〔〕byte(20000),0644)cmd。Process。Wait()} 通过对cgroup的配置,将容器中sh进程的CPU占用限制到了20ms(20)。topCpu(s):26。4us,1。4sy,0。0ni,72。2id,0。0wa,0。0hi,0。0si,0。0stPIDUSERPRNIVIRTRESSHRSCPUMEMTIMECOMMAND2668root20011328412081028R20。00。10:17。37sh三、UnionFileSystem UnionFileSystem简称是UnionFS,把其他文件系统联合到一个联合挂载点的文件系统服务。 可能你会对之前提到的MountNamespace产生混淆,MountNamespace是容器进程对文件系统挂载点的认知,这就意味着,只有挂载这个操作发生后,进程的视图才会被改变。在此之前,新创建的容器会直接继承宿主机的各个挂载点。3。1、chroot 在Linux操作系统中,chroot命令可以changerootfilesystem,即改变进程的根目录到你指定的位置。 它的使用方法也很简单。 假设,我们现在有一个HOMEtest目录,想要把它作为binbash进程的根目录 1、首先创建一个test目录和几个lib文件夹mkdirptestmkdirptest{bin,lib64,lib} 2、把bash命令拷贝到test目录下的bin路径下cpvbin{bash,ls}HOMEtestbin‘binbash’‘roottestbinbash’‘binls’‘roottestbinls’ 3、把bash命令需要的所有so文件拷贝到对应的lib路径下。可以使用ldd命令找到so文件THOMEtestlist(lddbinlsegrepolib。。〔09〕)docpvi{T}{i};done 4、最后执行chroot命令,把HOMEtest目录作为binbash进程的根目录chrootHOMEtestbinbash 这时,执行。binls命令,就会看到返回的是HOMEtest目录下的内容,而不是宿主机的内容。 对于被chroot的进程来说,它并不知道自己的根目录已经被修改了3。2、aufs aufs全称是AdvancedMultiLayeredUnificationFilesystem,主要功能就是将多个不同位置的目录联合挂载到同一个目录下。 例如,有两个文件夹A和B,他们分别有两个文件tree。AaxBbx 然后,用联合挂载的方式,将这两个目录挂载到公共目录C上mkdirCmounttaufsodirs。A:。Bnone。Ctree。C。Aabx3。3、docker使用的aufs 查看docker使用的存储dockerinfoStorageDriver:overlay2BackingFilesystem:extfsSupportsdtype:trueNativeOverlayDiff:trueuserxattr:falseLoggingDriver:jsonfileCgroupDriver:cgroupfsCgroupVersion:1 可以看到docker使用的是overlay2,docker在Linux上提供几种存储驱动程序: Driver 描述 overlay2 是所有支持的Linux发行版的首选存储驱动程序 fuseoverlayfs 仅适用于不提供rootless支持的主机上运行docker btrfs和zfs 允许高级选项,如创建快照 vfs 为了测试,并不能使用任何写时复制,性能较差,一般不用于生产 aufs 适用于ubuntu18。06或更早版本 deviemapper 需要directlvm,零配置但性能差,是以前的CentOS和RHEL中的存储驱动程序 overlay 旧overlay驱动程序用于不支持multiplelowerdir功能的内核 我们拉取镜像然后查看dockerpullcentosdockerimageinspectcentosGraphDriver:{Data:{MergedDir:varlibdockeroverlay225b7b27521d0dfa478b9173e0f39b726d914cebfb12a011d97093bf79f48201amerged,UpperDir:varlibdockeroverlay225b7b27521d0dfa478b9173e0f39b726d914cebfb12a011d97093bf79f48201adiff,WorkDir:varlibdockeroverlay225b7b27521d0dfa478b9173e0f39b726d914cebfb12a011d97093bf79f48201awork},Name:overlay2},RootFS:{Type:layers,Layers:〔sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59〕} 此时再rootfs的layers只有一层,接下来,以centos镜像为基础镜像,创建一个changedcentos的镜像。FROMcentosRUNechoHelloWorldtmpnewfile 然后编译镜像dockerbuildtchangedcentos。SendingbuildcontexttoDockerdaemon5。632kBStep12:FROMcentos5d0da3dc9764Step22:RUNechoHelloWorldtmpnewfileRunningin56f851769616Removingintermediatecontainer56f8517696169c6d811a36cdSuccessfullybuilt9c6d811a36cdSuccessfullytaggedchangedcentos:latest 然后查看生成的镜像信息dockerimageinspectchangedcentosGraphDriver:{Data:{LowerDir:varlibdockeroverlay225b7b27521d0dfa478b9173e0f39b726d914cebfb12a011d97093bf79f48201adiff,MergedDir:varlibdockeroverlay21962f3c14c7b78c087c80042cd0076686137a2bf4ae4d989d797813569a895a0merged,UpperDir:varlibdockeroverlay21962f3c14c7b78c087c80042cd0076686137a2bf4ae4d989d797813569a895a0diff,WorkDir:varlibdockeroverlay21962f3c14c7b78c087c80042cd0076686137a2bf4ae4d989d797813569a895a0work},Name:overlay2},RootFS:{Type:layers,Layers:〔sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59,sha256:4f3b7a0aae6d9b57d111bd1eb2e99ca900a36e2370b47266859e6ca4d97dd1f0〕} 可以看到layers新增一层dockerhistorychangedcentosIMAGECREATEDCREATEDBYSIZECOMMENT9c6d811a36cd2minutesagobinshcechoHelloWorldtmpnewfile12B5d0da3dc97643monthsagobinshc(nop)CMD〔binbash〕0Bmissing3monthsagobinshc(nop)LABELorg。labelschema。sc0Bmissing3monthsagobinshc(nop)ADDfile:805cb5e15fb6e0bb0231MB 从输出中可以看到,imagelayer最上层仅有12B大小,也就是说changedcentos镜像仅占用了12B的磁盘空间。四、总结 本文主要介绍了Docker运行的三大基石:Namespace、Cgroup和rootfs。 了解这些内容就能够清晰地明白docker和虚拟机的区别了,也就是说运行在Docker里的进程仍然需要宿主机的支持,比如内核版本等。