Docker是一种让程序运行在自己无法感知的容器中,用来隔离外部环境的工具。引入Docker最初是由dotCloud创始人SolomonHykes发起的内部项目,并于2013年3月在Apache2.0许可协议下开源,代码主要维护在GitHub上。Docker项目后来也加入了Linux基金会,成立了OpenContainerConsortium(OCI)来推动。Docker使用Google推出的Go语言开发实现,基于Linux内核的cgroup、namespace、UnionFS等技术。最初的实现是基于LXC。0.7版本后去掉了LXC,改用自研的libcontainer。从1.11开始,进一步进化为使用runC和containerd。2017年4月21日,PullRequest#32691将原来的Docker项目更名为Moby,Moby构建了DockerCE(社区版),而新的Docker项目构建了DockerEE(企业版)。Docker初步了解了Docker容器和虚拟机的区别。上图是DockerDoc中Docker与传统虚拟机区别的截图。Docker利用Linux内核的cgroup和namespace为程序的执行创建一个隔离的环境,使程序感知不到外界的存在,仍然运行在原有的内核上;而虚拟机通过Hypervisor模拟一个环境。在整个系统环境中,虚拟机中的程序运行在虚拟机内核之上。由于虚拟机需要模拟完整的操作系统环境,因此开销比Docker容器高很多很多。你可以把运行在容器中的程序想象成杜鲁门(杜鲁门的英雄世界),他并不知道自己生活在一个布置良好的超大工作室里,但他仍然生活在现实世界中,呼吸着空气在现实世界中和我们吃同样的食物;虚拟机中运行的程序就像卡通里的小猪佩奇一样,他的一切都是虚拟的,虽然小猪佩奇不知道我生活在卡通里,但是很明显这里和我们的世界完全不一样(不是相同的系统内核)。性能差异特征容器虚拟机启动分秒分分钟硬盘使用量一般为MB,一般为GB性能接近原系统支持容量单机几千个容器,一般有几十个Docker基本概念镜像是一个完整的根文件系统包含操作系统的只读打包文件,结合了多层文件系统。Docker提供了一套完整的根文件系统,让应用程序可以在没有感知的情况下运行在容器中。例如官方镜像库/ubuntu,包含了一套完整的根文件系统。apache和nginx之类的东西都是基于这个图像构建的。由于library/ubuntu本身就很大,Docker采用了分层存储的方式。本文假设您已经安装了Docker。上图使用dockerpullnginx从官方Registry(下面会提到)拉取nginx镜像。拉取nginx相当于library/nginx:latest。library表示nginx是官方镜像,可以省略,:latest表示拉取label为最新的镜像。pull之后可以看到有两个镜像,因为nginx镜像本身就是基于library:ubuntu:16.04的镜像。上图中通过dockerpullhttpd拉取了apache镜像。由于本地已经存在ubuntu:16.04镜像,拉取时不会重复拉取。这节省了拉取时间。这就是Docker分层存储的意义所在。只读镜像可以理解为之前的CD,不能更改。为了模拟刻录光盘的功能,会建立一个两层的文件系统,一层是光盘的只读文件系统;另一个是用于存储更改数据的可写文件系统。从而模拟出改变镜像的效果。Docker也采用了这种类似的分层方法。如图所示,可以看出ubuntu:15.04是由很多层文件系统(镜像)堆叠而成,最底层是根文件系统(d3a1f33e8a5a)。这些文件系统层被设置为只读。多层文件系统使用了上面提到的UnionFS、AUFS、OverlayFS。这是一种文件系统。这种联合挂载文件系统首先用于解决CD只读文件系统的修改问题。Docker之前使用过AUFS,但是因为AUFS不被linus喜欢(linus评价为dense,不可读,无注释),所以一直没有将AUFS合并到Linux的主分支中。Docker在1.12之后将默认的文件系统从AUFS替换为OverlayFS2。因为OverlayFS2已经合并到Linux的主分支中。我们将容器上的nginx镜像拉取到本地,我们可以使用dockercontainerstartnginx(省略latest标签)来运行这个镜像。在运行之前,会先创建一个容器(其实就是创建一层可读写的文件系统,为程序运行时提供读写支持),然后启动程序,让程序运行在一个隔离环境(不是虚拟环境)里面。也可以通过dockercontainercommit>提交当前层(和Git提交一样)形成一个新的镜像,但是不推荐这种方式;这是因为有些Junk文件,如果提交了这些垃圾文件,新的图片无法修改,只会增加图片的体积。下面将介绍如何创建镜像。可以看到在上图中创建容器的时候,其实是创建了一个容器可读写层。也可以使用dockercontainerstop来停止容器的运行,相当于杀掉容器中正在运行的程序,但是创建容器时创建的可读写文件系统仍然存在。所以你仍然可以用dockerstart重启程序。仓库镜像搭建好后,可以方便的在宿主机上运行,??但是如果其他机器要使用这个镜像,我们需要一个集中存储和分发镜像的服务。DockerRegistry就是这样一种服务。一个DockerRegistry可以包含多个仓库,每个仓库可以包含多个标签,每个标签对应一个镜像。以上面的library/nginx:latest为例,library表示这个镜像是官方镜像,如果不是官方镜像,一般填写在DockerRegistry注册的用户名;library/nginx为仓库名称,latest为仓库标签。确实DockerRegistry官方是全球最大的镜像分发服务,同时也提供DockerRegistry官方镜像搭建私有镜像分发服务。而且DockerHub和社区已经产生了大量高质量的镜像,这使得我们构建镜像更加容易。Docker简单实践Docker单镜像前面提到可以通过dockercommit生成一个新的镜像,但是不推荐这种方式(原因已经说明),所以我们一般使用Dockerfile的方式。下面的实践以github-issue-rss为例,演示如何容器化一个普通的项目。首先创建一个Dockerfile,内容如下:FROMnode:9-alpineMAINTAINERmrcode"mrcodehang@outlook.com"WORKDIR/src#表示程序在容器中运行的当前目录COPY。/src#构建Dockerfile目录下的文件全部复制到镜像的/src目录下RUNnpminstall-gyarn&&yarninstall#构建时执行EXPOSE3000#将容器的3000端口暴露到外部ENTRYPOINT["npm","start"]#执行dockerstartnpmstartDockerfile中每行开头的大写字母单词称为Dockerfile指令。每执行一条指令,都会增加一层image(其实质就是执行一次dockercommit,AUFS最大层数是127层,所以Dockerfile层数最好不要太多!FROM表示在哪个镜像上构建,node:9-alpine表示基于官方node镜像构建,标签9-alpine表示这是一个node9镜像,同时构建的是node9镜像基于alpine镜像,Alpine是Linux的精简版,大小只有5MB左右,Ubuntu镜像接近200MB,构建镜像时会执行RUN命令,&&符号用于缩减RUN命令的使用次数和最终镜像的层数。EXPOSE命令允许外界通过容器的3000端口进行通信。ENTRYPOINT表示在dockerstart时会执行npmstart(启动程序)被执行,也可以写成ENTRYPOINTnpmstart的形式;然后就可以开始施工了。有同学喜欢在npmstart后加'&',让容器默认在后台运行;但这只会导致容器无法启动,因为容器本身的执行完全依赖于程序本身的进程。当程序本身的进程没有挂载到docker容器上时,容器会直接结束,容器结束后,容器中的进程也被杀死。所以你必须知道,是容器中的进程在维持容器的运行!有一个'.'图中执行命令的最后,就是将当前目录作为上下文传递给Dockerdaemon;Docker的工作方式是基于C-S架构。需要将build所在的目录传递给dockerdaemon,也就是上面Dockerfile中COPY命令的当前目录。接下来,创建一个容器。一个镜像可以创建多个容器(其实就是在同一层创建多个读写层)。dockerrun会拉取远程镜像(如果不是本地),然后会根据mrcode/github-issue-rss:test镜像创建容器(只能省略latest标签);-v会创建一个数据卷(volume),也就是说当容器向/var/log/github-issue-rss/写入数据时,相当于写入到的~/github-issue-rss/log目录下宿主机,从而保持容器的无状态特性(无状态特性是指容器尽量不要在容器运行的读写层存储重要数据。虽然是读写层,但是用于存放程序运行时产生的临时文件,重要的数据不应该存放在读写层中的数据);-d表示daemon执行程序,否则容器进程会挂载到当前shell,一般通过-d挂载到dockerdaemon进程;--rm表示容器退出后自动删除容器,推荐使用,也是容器无状态的一种体现。容器进程与容器中程序的进程具有相同的生命周期。容器进程用于启动容器中的程序,相当于Linux中的init进程;当容器中的程序被dockerstop杀死后,容器就会退出,留下创建的读写层文件系统,这也是容器存在的标志。由于创建容器只是创建了一个可读写的文件系统,所以容器的存在是非常非常轻量级的。即使为一个镜像创建了多个容器,镜像本身也不会被再次复制,而是最大程度的复用,因为镜像中多层文件系统的每一层都被设置为只读。可以通过dockercontainerls查看所有当前运行的容器,如果想查看退出的容器,加一个-a参数即可。使用dockercontainerstart/stop启动/关闭容器。最后可以通过dockerpushmrcode/github-issue-rss:test发布到DockerHub并与社区分享。Dockermultiplemirrorsgithub-issue-rss是一个将GitHub上的issues转换为RSS的工具。这个工具需要用到mysql。为了以后方便数据迁移,我决定使用mysql镜像。mysql镜像可以将所有状态存储在宿主机文件夹中。那么我现在不仅需要启动mysql和github-issue-rss镜像,还需要建立它们之间的网络连接关系,事情就变得麻烦了。有一个名为docker-compose的工具(本文假装您已经安装了它)可以自动执行此操作。这是项目根目录中的docker-compose.yml文件:version:"3"services:db:image:mysql:5.7volumes:-~/.github-issue-rss/mysql:/var/lib/mysql重新启动:始终环境:MYSQL_ROOT_PASSWORD:rootrootMYSQL_DATABASE:rssMYSQL_USER:mrcodeMYSQL_PASSWORD:github-issue-rssgithub-issue-rss:图像:mrcode/github-issue-rss:v0.1.0depends_on:-db端口:-“3000:3000“重新启动:始终环境:MYSQL_PORT:3306MYSQL_HOST:dbMYSQL_SCHEMA:rssMYSQL_USERNAME:mrcodeMYSQL_PASSWORD:github-issue-rssLOG_FILE:/var/log/github-issue-rss/volumes:-~/.github-issue-rss/log/:/var/log/github-issue-rss/在docker-compose的世界里没有容器,只有服务。它认为它启动了两个服务db和github-issue-rss。没有人是服务的主宰,所有的服务都是平等的。在dbservice中,设置了volumes,mysql的数据存放在~/.github-issue-rss/mysql/,可以设置更多的volumes。restart表示只要服务执行失败就重新启动,防止依赖的服务还没有启动导致的错误导致的连锁反应。为两个服务配置环境,建立两者之间的数据连接。github-issue-rss代码将读取此环境变量,然后连接到db服务。可以看到github-issue-rss中的环境变量MYSQL_HOST设置为db,这是因为docker-compose会在启动的服务配置中建立这个DNS映射关系。也可以通过docker-composedown停止删除服务对应的容器。现在您只需要在本地克隆存储库并执行docker-composeup以启动github-issue-rss,因为github-issue-rss图像本身已经构建并发布到DockerHub。Docker的应用持续集成和持续交付使用Docker通过自定义应用镜像来实现持续集成、持续交付和部署。开发者使用Dockerfile构建镜像,结合持续集成系统进行集成测试,运维人员可以快速将镜像部署到生产环境中。甚至可以结合持续部署来实现自动化部署。而且,使用Dockerfile让镜像的构建透明化,不仅可以帮助开发者了解应用运行环境,也可以方便运维团队了解应用运行所需要的条件,有利于更好的部署镜像。生产环境。微服务Docker和微服务架构是很自然的。从Docker的角度来看,软件的本质是容器的组合:业务逻辑容器、数据库容器、存储容器、队列容器……Docker把软件拆分成几个标准化的容器。然后像搭积木一样搭起来。这正是微服务的思想:软件将任务外包,让各种外部服务来完成这些任务,而软件本身只是底层服务的呼叫中心和组装层。参考文章http://www.ruanyifeng.com/blo...byRuanYifenghttps://github.com/yeasy/dock...byBaohuaYanghttps://docs.docker.combyDockerDoc