容器,目前最火的话题了,在后端的开发中,容器的运用也已是主流技术了,今天,咱们就来讲说容器技术,以前我对这一块的了解不是不少,可是最近有些特殊缘由转成运维工程师了,而公司的全线服务都是docker的,以一个开发人员的习惯,转成运维之后,仍是想对这种东西总想深刻了解一下,因而看了很多相关资料而且看了一下docker的源代码,发现这东西确实很厉害,和以前脑中的docker印象彻底不一样,因而有了这篇文章。linux
先说结论,容器真的很好,很轻量级,功能又很重量级。git
首先,虽然目前docker技术如此火爆,可是其实容器技术本上并非什么高大上的东西,总的来说,就是对目前的Linux底层的几个API的封装,而后围绕着这几个API开发出了一套周边的环境。程序员
以前全部的讲关于容器的文章,一开始就开始讲UTC隔离,PID隔离,IPC隔离,文件系统隔离,CGroups系统,今天这一篇,咱们换一个视角,咱们从如下几个方面来讲一下容器技术。github
要说容器,跑不了和虚拟机进行比较,虚拟机是比较古老的技术了,虚拟机的架构图以下所示。golang
虚拟机核心是什么?是模拟硬件,让虚拟机的操做系统觉得本身跑在一个真实的物理机器上,用软件模拟出来CPU,内存,硬盘,网卡
,让虚拟机里面的操做系统以为本身是在操做真实的硬件,因此虚拟机里面的CPU啊,内存啊都是假的,都是软件模拟出来的(如今有硬件虚拟化技术了,比纯软件模拟要高级一些,但操做系统无论这些),既然操做系统都骗过去了,固然跑在操做系统上的进程一样也骗过去了呗,因此这些进程都彻底感知不到底层硬件的区别,还觉得本身很欢乐的跑在一台真实的物理机上了。redis
那么容器又是什么鬼呢?容器的架构图以下(这张图网上找的,侵权立刻删)docker
和虚拟机一个很明显的区别就是容器其实并无模拟硬件
,仍是那个硬件,仍是那个操做系统,只不过是在操做系统上作了一点文章【这张图中就是docker engine了】,让进程觉得本身运行在了一个全新的操做系统上
,有一个很形象的词来描述他就是软禁
!就是把进程软禁在一个环境中,让进程以为本身很happy,其实一切尽在操做系统的掌控之中,其实虚拟机也是,虚拟机是把操做系统软禁起来了,让操做系统以为很happy,容器是把进程软禁起来,你看,一个是软禁操做系统,一个是软禁进程,这两个明显不是一个级别的东西,谁轻谁重不用说了吧。shell
既然容器和虚拟机的区别在于一个是经过模拟硬件来软禁操做系统,一个是经过作作操做系统的手脚来软禁进程
,那么他们能达到的效果也是不同的。bootstrap
uname -a
看到的内核版本和外面看到的是同样的。本质上容器就是一个进程,他和宿主机上任何其余进程没什么本质的区别。如何来作一个容器呢?或者说容器是怎么实现的呢?咱们从几个方面来讲一下容器的实现,一是最小系统,二是网络系统,三是进程隔离技术,四是资源分配。最小系统告诉你软禁进程所须要的那个温馨的监狱环境,网络系统告诉你软禁的进程如何和外界交互,进程隔离技术告诉你若是把进程关到这个温馨的监狱中去,资源分配告诉你监狱里的进程如何给他分配资源让他不能胡来。
ubuntu
要软禁一个进程,固然须要有个监狱了,在说监狱以前,咱们先看看操做系统的结构,一个完整的操做系统【Linux/Unix操做系统】分红三部分,以下图所示【本图也是网上找的,侵权立刻删,这个图是四个部分,包括一个boot参数部分,这不是重点】。
首先是bootloader
,这部分启动部分是汇编代码,CPU从一个固定位置读取第一行汇编代码开始运行,bootloader先会初始化CPU,内存,网卡(若是须要),而后这部分的主要做用是把操做系统的kernel
代码从硬盘加载到内存中,而后bootloader
使命完成了,跳转到kernel
的main函数入口开始执行kernel
代码,kernel就是咱们熟悉的linux的内核代码了,你们说的看内核代码就是看的这个部分了,kernel代码启动之后,会从新初始化CPU,内存,网卡等设备,而后开始运行内核代码,最后,启动上帝进程(init),开始正常运行kernel,而后kernel会挂载文件系统
。
好了,到这里,对进程来讲都是无心义的,由于进程不关心这些,进程产生的时候这些工做已经作完了,进程能看到的就是这个文件系统
了,对进程来讲,内存空间,CPU核心数,网络资源,文件系统是他惟一能看得见使用获得的东西,因此咱们的监狱环境就是这么几项核心的东西了。
kernel和文件系统是能够分离的,好比咱们熟悉的ubuntu操做系统,可能用的是3.18的Linux Kernel,再加上一个本身的文件系统,也能够用2.6的Kernel加上一样的操做系统。每一个Linux的发行版都是这样的,底层的Kernel可能都是同一个,不一样的只是文件系统不一样,因此,能够简单的认为,linux的各类发行版就是kernel内核加上一个独特的文件系统,这个文件系统上有各类各样的工具软件。
既然是这样,那么咱们要软禁一个进程,最基础的固然要给他一个文件系统啦,文件系统简单的说就是一堆文件夹加上一堆文件组成的,咱们先来生成一个文件系统,我以前是作嵌入式的,嵌入式的Linux系统生成文件系统通常用busybox
,只须要在在ubuntu上执行下面的命令,就能生成一个文件系统
apt-get install busybox-static mkdir rootfs;cd rootfs mkdir dev etc lib usr var proc tmp home root mnt sys /bin/busybox --install -s bin
大概这么几步就制做完成了一个文件系统,也就是监狱的基本环境已经有了,记得把lib文件夹的内容拷过去。制做完了之后,文件系统就这样了。
还有一种方式,就是使用debootstap
这个工具来作,也是几行命令就作完了一个debian的文件系统了,里面连apt-get都有,docker的基础文件系统也是这个。
apt-get install qemu-user-static debootstrap binfmt-support mkdir rootfs debootstrap --foreign wheezy rootfs //wheezy是debian的版本 cp /usr/bin/qemu-arm-static rootfs/usr/bin/
完成之后,这个wheezy的文件系统就是一个标准的debian的文件系统了,里面的基本工具包罗万象。
OK,基本的监狱环境已经搭建好了,进程住进去之后就跟在外面同样,啥都能干,但就是跑不出来。
要测试这个环境,可使用linux的chroot
命令,chroot ./rootfs
就进入了这个制做好的文件系统了,你能够试试,看不到外面的东西了哦。
刚刚只创建了一个基本的监狱环境,对于现代的监狱,只有个房子不能上网怎么行?因此对于监狱环境,还须要创建一个网络环境,好让里面的进程们能够很方便的和监狱外的亲友们联系啊,否则谁愿意一我的呆在里面啊。
如何来创建一个网络呢?对于容器而言,不少地方是可配置的,这里说可配置,其实意思就是可配置也能够不配置,对于网络就是这样,通常的容器技术,对网络的支持有如下几个方式。
咱们能够直接使用第二种不配置模式,直接使用宿主机的网络,这也是最容易最方便的,可是咱们在这里说的时候稍微说一下第三种的网桥模式吧。
网桥最开始的做用主要是用来链接两个不一样的局域网的,更具体的应用,通常是用来链接两个不一样的mac层的局域网的,好比有线电视网和以太网,通常网桥只作数据的过滤和转发,也能够适当的作一些限流的工做,没有路由器那么复杂,实现起来也比较简单,对高层协议透明,他能操做的都是mac报文,也就是在ip层如下的报文。
对于容器而言,使用网桥的方式是在宿主机上使用brctl
命令创建一个网桥,做为容器和外界交互的渠道,也就是你们使用docker的时候,用ifconfig
命令看到的docker0网卡,这实际上就是一个网桥,而后每启动一个容器,就用brctl
命令创建一对
虚拟网卡,一块给容器,一块连到网桥上。这样操做下来,容器中发给虚拟网卡的数据都会发给网桥,而网桥是宿主机上的,是能链接外网的,因此这样来作到了容器内的进程能访问外网。
容器的网络我没有深刻研究,感受不是特别复杂,最复杂的方式就是网桥的方式了,这些网络配置均可以经过命令行来进行,可是docker的源码中是本身经过系统调用实现的,说实话我没怎么看明白,功力仍是不够啊。 我使用的就是最最简单的不隔离,和宿主机共用网卡,只能经过端口来区分不一样容器中的服务。
好了,监狱已经建好了,探视系统也有了,得抓人了来软禁了,把进程抓进来吧。咱们以一个最最基本的进程/bin/bash
为例,把这个进程抓进监狱吧。
说到抓进程,这时候就须要来聊聊容器的底层技术了,Linux提供几项基础技术来进行轻量级的系统隔离,这些个隔离技术组成了咱们熟悉的docker的基础。本篇不会大段的描述这些技术,文章后面我会给出一些参考连接,由于这类文章处处均可以找到,本篇只是让你们对容器自己有个了解。 下面所说的全部基础技术,其实就是一条系统调用,包括docker的基础技术,也是这么一条系统调用(固然,docker还有不少其余的,可是就容器来讲,这条是核心的了)
clone(进程函数, 进程栈空间, CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET |CLONE_NEWUSER | CLONE_NEWIPC , NULL)
这是一条C语言的clone系统调用,实际上就是启动一个新的进程,后面的参数就是各类隔离了,包括UTS隔离,PID隔离,文件系统隔离,网络隔离,用户隔离,IPC通信隔离
。
在go语言中,没有clone
这个系统调用(不知道为何不作这个系统调用,多是为了多平台的兼容吧),必须使用exec.Cmd
这个对象来启动进程,在linux
环境下,能够设置Cmd的attr属性,其中有个属性叫CloneFlags
,能够把上面那些个隔离信息设置进去,这样,启动的进程就是咱们须要的了,咱们能够这么来启动这个进程
cmd := exec.Command("./container", args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET,
}
cmd.Run()复制代码
这样,经过这个cmd命令启动的./container
进程就是一个隔离进程了,也就是咱们把这个进程给关起来了,他已经看不到其余东西了,是否是很简单?可是你要是就直接这么运行,仍是看不到什么特别的地方。 在这个以后,咱们须要按照上面所说的,把监狱先创建好,监狱的创建在./container
中进行,创建监狱也比较简单,基本上也是一堆系统调用,好比文件系统的软禁,就像下面的同样
syscall.Mount(rootfs, tmpMountPoint, "", syscall.MS_BIND, "") //挂载根文件系统
syscall.Mount(rootfs+"/proc", tmpMountPoint+"/proc", "proc", 0, ""); //挂载proc文件夹
syscall.PivotRoot(tmpMountPoint, pivotDir) //把进程软禁到根文件系统中复制代码
关于上面proc文件夹,作了特殊处理,在Linux中,proc文件夹的地位比较特殊,具体做用能够自行查文档,简单的说就是保存系统信息的文件夹。在这里,dev
和sys
这两个特殊文件夹也须要作特殊处理的,这里没有写出来而已。
这些都作完了之后,就能够启动真正须要执行的进程了,好比/bin/bash
,或者你本身的程序,这样启动的/bin/bash
或者你本身的程序就是在监狱中启动的了,那么他看到的全部东西都是监狱中的了,外面宿主机的一切对他来讲都是屏蔽的了,这样,一个docker的雏形就产生了。
这里多说一下,经过clone系统调用启动的进程,它本身看到本身的PID是1,也就是上帝进程了,这个上帝进程能够来造基础监狱【文件系统】,打造放风系统【网络系统】,而后再经过它来生成新的进程,这些进程出来就在监狱中了,咱们使用docker的时候,本身的服务实际上就是这些个在监狱中出生的进程【可能个人描述不太正确啊,我没有仔细看docker的源码,我本身感受是这样的】。
至此,咱们来总结一下,启动一个最简单的容器并运行你本身的进程,须要几步。
Cloneflags
的值,并经过exec.Cmd来进行上帝进程的启动经过上面几步,最简容器就完成了,是否是很简单?可是容器仅仅有这些是不够的,咱们还有三个隔离没有讲,这里稍微提一下吧。
咱们知道,通常的监狱中的食物是定量的,毕竟不是每一个监狱均可以吃自助餐的,容器也同样,要是咱们就启个容器啥都不限制,里面要是有个牛逼的程序员写的牛逼程序,瞬间就把你的内存和CPU给干没了。好比像下面这个fork炸弹。【下面程序请不要尝试!!】
int main(){
while(fork());
}复制代码
在容器技术中,Cgroups【control groups】
就是干这个事情的,cgroups
负责给监狱设定资源,好比能用几个cpu啊,cpu能给你多少百分比的使用量啊,内存能用多少啊,磁盘能用多少啊,磁盘的速度能给你多少啊,各类资源均可以从cgroups
来进行配置,把这些东西配置给容器之后,就算容器里面运行一个fork炸弹也不怕了,反正影响不到外面的宿主机,到这里,容器已经愈来愈像虚拟机了。
cgroups
是linux内核提供的API,虽然是API,但它的整个实现完美知足了Linux两大设计哲学之一:一切皆文件(还有一个哲学是通信全管道)
,对API的调用其实是操做文件。
咱们以cpu的核心数看看如何来作一个cgroups
的资源管理。假设咱们的物理机是个8核的CPU,而咱们刚刚启动的容器我只想让他使用其中的两个核,很简单,咱们用命令行直接操做sys/fs/cgroups文件夹下的文件来进行。这个配置咱们能够在启动的上帝进程中进行,也能够在容器外部进行,都是直接操做文件。
关于cgroups
这个东西很复杂也很强大,其实在容器出来以前,好的运维工程师就已经把这个玩得很溜了。docker也只是把这些个文件操做封装了一下,变成了docker的启动和配置参数而已。
好了,该说的都说了,咱们来实战一把,本身启一个容器吧,而且启动之后为了更直观的看到效果,咱们启动一个ssh服务,打开22332端口,而后外面就能够经过ssh连到容器内部了,这时候你爱干什么干什么了。
文件系统制做咱们直接使用debootstrap
进行制做,在/root/目录下创建一个rootfs
的文件夹,而后使用debootstrap --foreign wheezy rootfs
制做文件系统,制做完了之后,文件系统就是下面这个样子
初始化脚本就作两件事情,一是启动ssh服务,一是启动一个shell,提早先把/etc/ssh/sshd_config
中的端口改为23322。
#!/bin/bash
service ssh start
/bin/bash复制代码
而后把这个脚本放到制做的文件系统的root目录下,加上执行权限。
文件系统制做完成了,启动脚本也作完了,咱们看看咱们这个容器的架构,架构很简单,整个容器分为两个独立的进程,两份独立的代码。
exec.Cmd
这个包启动这个进程。/root/start_container.sh
)第二个进程是容器的上帝进程,在这里进行文件系统的挂载,最重要的代码以下
syscall.Mount(rootfs, tmpMountPoint, "", syscall.MS_BIND, "") //挂载根文件系统
syscall.Mount(procpath, tmpMountPointProc, "proc", 0, "") //挂载proc文件夹,用来看系统信息的
syscall.Mount(syspath, tmpMountPointSys, "sysfs", 0, "") //挂载sys文件夹,用来作权限控制的
syscall.Mount("udev", tmpMountPointDev, "devtmpfs", 0, "") //挂载dev,用来使用设备的
syscall.PivotRoot(tmpMountPoint, pivotDir)//进入到文件系统中复制代码
具体代码能够看github上的文件,这样,根文件系统就挂载完了,已经进入了基本监狱中了。
文件系统挂载完了之后,而后启动初始化脚本,这个就比较简单了,一个exec.Cmd的Run方法调用就搞定了。
cmd := exec.Command("/root/start_container.sh")复制代码
这样,ssh服务就在容器中启动了,能够看到一行Starting OpenBSD Secure Shell server: sshd.
的打印信息,容器启动完成,这时候,咱们能够经过ssh root@127.0.0.1 -p 23322
这个命令登陆进咱们的容器了,而后你就能够随心所欲了。
上面那个图,咱们看到登陆进来之后,hostname已经显示为咱们设定的hello
了,这时这个会话已经在容器里面了,咱们ps一下看看进程们。
看到pid为1的进程了么,那个就是启动这个容器的上帝进程了。恩,到这里,咱们已经在容器中了,这里启动的任何东西都和咱们知道的docker中的进程没什么太大区别了。
但在这里,我缺失了权限的部分,你们能够本身加上去,主要是各类文件操做比较麻烦。。。
docker这门最近两年很是火的技术,光从容器的角度来看的话,也不算什么新的牛逼技术了,和虚拟机比起来仍是要简单很多,固然,docker自己可彻底不止容器技术自己,还有AUFS
文件分层技术,还有etcd
集群技术,最关键的是docker经过本身的整个生态把容器包裹在里面了,提供了一整套的容器管理套件,这样让容器的使用变得异常简单,因此docker才能这么流行吧。
和虚拟机比起来,docker的优势实在是太多了。
首先,从易用性的角度来讲,管理一个虚拟机的集群,有一整套软件系统,好比openstack这种,光熟悉这个openstack就够喝一壶的了,并且openstack的网络管理异常复杂,哦,不对,是变态级的复杂,要把网络调通不是那么容易的事情。
第二,从性能上来看看,咱们刚刚说了容器的原理,因此实际上容器无论是对CPU的利用,仍是内存的操做或者外部设备的操做,对一切硬件的操做实际上都是直接操做的,并无通过一个中间层进行过分,可是虚拟机就不同了,虚拟机是先操做假的硬件,而后假硬件再操做真硬件,利用率从理论上就会比容器的要差,虽然如今有硬件虚拟化的技术了能提高一部分性能,但从理论上来讲性能仍是没有容器好,这部分我没有实际测试过啊,只是从理论上这么以为的,若是有不对的欢迎拍砖啊。
第三,从部署的易用性上和启动时间上,容器就彻底能够秒了虚拟机了,这个不用多说吧,一个是启动一台假电脑,一个是启动一个进程。
那么,docker和虚拟机比起来,缺点在哪里呢?
我本身想了半天,除了资源隔离性没有虚拟机好之外,我实在是想不出还有什么缺点,由于cgroups
的隔离技术只能设定一个上限,好比在一台4核4G的机器上,你可能启动两个docker,给他们的资源都是4核4G,若是有个docker跑偏了,一我的就干掉了4G内存,那么另一个docker可能申请不到资源了。而虚拟机就不存在这个问题,可是这也是个双刃剑,docker的这种作法能够更多的榨干系统资源,而虚拟机的作法极可能在浪费系统资源。
除了这个,我实在是想不出还有其余缺点。网上也有说权限管理没有虚拟机好,但我以为权限这东西,仍是得靠人,靠软件永远靠不住。
最后,代码都在github上,只有很是很是简单的三个文件【一个Container.go是容器类,一个wocker.go没内容,一个startContainer.go启动容器】,那个http服务留着没写,后面写http服务的时候在用一下。
恩,docker确实是个好东西。
若是你以为不错,欢迎转发给更多人看到,也欢迎关注个人公众号,主要聊聊搜索,推荐,广告技术,还有瞎扯。。文章会在这里首先发出来:)扫描或者搜索微信号XJJ267或者搜索西加加语言就行