【Docker】
Docker可以说是近几年非常热门的技术之一了。不管是别人敦促我还是从自己的想法来说,都觉得Docker这玩意儿肯定是要好好学习一下的,无奈没啥时间专门播出来给Docker,一直以来都是要用了就问人或者百度百度,用的也都是最简单的一些功能。
但是始终觉得,任何一种知识,不系统性地学习一下是掌握不好的。所以这次特地按照《第一本Docker书》这本新手向的书为框架,系统性地学习学习。
■ 参考好文
①10图了解Docker简单原理:http://dockone.io/article/783
■ 什么是Docker
如果从技术实现细节(即使不是细节,是大体框架)而言,这个命题估计可以写上三天三夜,何况我也写不出来。但是好在作为一个使用者,(至少在目前阶段),我们只需要了解最基本的一些概念,对Docker这个东西有些感觉就好了。各种Docker介绍的书或者文档都把Docker写得特别高大上,然而在我一个小白的角度看来,docker(容器)就是个轻型虚拟机嘛。。
服务器的演变历程是从物理机编程虚拟机,再由虚拟机变成Docker这类容器技术。所以虚拟机到Docker可谓是一脉相承的。
毫无疑问Docker也肯定是要运行在物理主机上的。从架构来看,通过docker进行工作需要物理主机上有一个守护进程,这个进程用于和外界的docker客户端互动以及管理主机上的镜像、容器等等各类资源。这些资源可以方便地增删启停,而且docker容器的性能也不错,可以说比虚拟机强很多,所以一个主机上可以运行很多台docker容器,因此应用docker还有助于榨干服务器的性能。一个docker容器/镜像具有很强的独立性(进程环境隔离、网络隔离),且两者之间可以互相转化。镜像时静态的容器时动态的。这就表明用docker可以方便地将一整套虚拟系统包括环境和程序在内方便地复现到其他主机上,只要那里也支持docker。由于docker的独立性和迁移方便的特性,使得docker的另一大主题是PaaS和微服务架构。docker推荐每个容器中只运行一个进程,然后将若干个容器互相关联起来组成一个原来整块部署在主机上的应用。这样做似乎增加了容器间通信以及管理容器的额外成本,但是使得分布式部署应用以及部署变化时更加方便稳定,降低了不同应用模块之间的耦合性。
基于上面说的Docker特点,除了微服务,Docker的容器/镜像还可以用来做很多事,比如:
加快DevOps流程,省去了不同环境的搭建和配置调试,通过镜像将环境一键再现
用Docker创建沙箱环境,用于测试、开发、教学等。
根据Docker中的内容提供SaaS服务
...
■ Docker资源原理小解
参考文①对docker的各个资源做了比较形象和易懂的解释。我以自己的理解概括一下然后补充一点实验所得:
首先可以明确,Docker中的镜像和容器本质上是一个特殊的文件系统。传统意义上的虚拟机从操作系统开始虚拟,文件系统建设在操作系统之上。我们普通用户通常关注和使用的就是文件系统(甚至更加高层的应用)。由于虚拟机从操作系统层面开始虚拟,所以如果我们要部署多个文件系统(or应用)那就要部署多个虚拟机,这也就说要部署多个操作系统。一个操作系统是很大,但我们真正使用的一个文件系统却很小。
而Docker比虚拟机牛逼的地方就在于利用Docker引擎,成功把文件系统直接建设在一个引擎上。这样子的话部署多个文件系统的代价就小了很多。换言之,更多的计算资源被有效地利用了。
接下来就一种很重要的元素,叫镜像层(image layer)。镜像层的作用是记录了一些对Docker虚拟出来的文件系统的变动。具体的实现形式可能是若干个文件加上对这些文件的操作的集合。观念上来说,可以理解成为一个虚拟文件系统的增量更新包吧。镜像层,顾名思义,多个镜像层可以构成一个镜像。比如一开始基于一个基础镜像,然后不断打“增量更新包”,逐渐地变成一个专业地,有用的镜像。对于组成了镜像的若干个镜像层(可以说是狭义上的镜像层)而言,他们具有这样的特点:
1. 只读不可写。镜像层不可写的意思是对于这个“增量更新包”,一旦创建我们就无法修改其细节,除非重新创建
2. 叠加全透明。虽然一个镜像可能是由若干个镜像层组成,但是我们无法但从中抽取出一个层来处理。对于用户而言,各个镜像层永远是透明的,而镜像就是一个整体。
容器,和镜像非常相似,也有若干镜像层构成。只不过生成容器时,在其依据镜像的那些镜像层的基础上增加了一层可写的镜像层。容器的镜像层具有上述特点2,同时除去最上层的读写层也符合特点1。针对这个容器特有的读写层,也有一些东西必须指出:
读写层说明我们可以在此层中做出删除操作,但是基于特点1的只读特性,我们不可能真的在某一层中去掉添加这个文件的步骤。所以Docker设计成了在容器的读写层中,做出的删除操作只是打了个删除标记,另被删除的东西对用户不可见,这也就解释了为什么在容器中删除文件并不能让宿主机的磁盘也空出一块空间,反而还会再占据一小点空间(记录删除标记时用的)。容器占有的这些空间,只有在容器本身被删除时,所有镜像层都被删掉的时候才会被释放出来。
而对于镜像,我们只能说,如果不断地更新镜像,镜像只会越来越大,能够控制的是尽量让镜像size增加速度放缓。这也是为什么在docker history 一个镜像的时候size字段永远是正值的原因。
● 镜像层的结构
一个镜像层除了包含上面所说的“增量包”的信息之外还有一些其他的信息。比如他有一个唯一的ID,通常这个ID也就是镜像本身的ID。因为只有在同样基础上新增的一层同样的镜像层才有可能会产生同样的ID,所以这个ID直接拿来标记镜像也没有问题。
镜像层还有一些所谓的Metadata,这些数据通常以JSON文件的形式存放在宿主机系统中,主要记录了Docker在运行,新建资源时的一些信息以及层和层之间的层级信息。
此外镜像层还有一个指针指向其父镜像层。如果一个镜像层没有指针那么说明它是最底层的镜像层了。
镜像层之间的关系犹如此图:
镜像层可以共用,即不同的镜像如果有相同的底层镜像层们的话,这些底层镜像层是只占据一次空间的。比如镜像A大小为1G,如果在加入一个0.1G大小的文件为基础的情况下创建镜像B,两个镜像总大小并不是2.1G而是1.1G。删除镜像的时候,删除的也是那些镜像独自占有的镜像层,共用的镜像层不会被删除。
● docker各个命令的原理化解读
基于上面说明的这样一种概念,参考文①中给出了大部分docker命令的原理性的解释。这里不全都罗列出来了。写出几个我觉得比较意外的吧。
docker commit 是将容器的读写层转化为可读层,这其实也就是将容器转化为镜像的过程。
docker build 是根据dockerfile定义的规则向现有的镜像上增加若干层只读镜像层,dockerfile中每一条指令都代表这一层镜像层,这一点很重要。
docker inspect 据说给出的详细信息就是当前容器/镜像最上面那层的镜像层的Metadata
docker save 说到save就不得不和export来对比一下。save将一个镜像的所有镜像层数据包括Metadata都压缩保存进一个文件中。所以把这个文件放到另一台主机上时就可以复制出一个一模一样的镜像(docker history查看该镜像可以看到完整的镜像层叠加历史),相对的docker export将一个容器的所有镜像层都整合到一个镜像层中,相当于重新创建了一个从最底层镜像层开始的基础镜像。两者各有利弊,需要按照具体需求使用。值得一提的是,当若干个镜像层整合到一个镜像层的过程,这个镜像层所体现出来的文件系统以当前容器表观文件系统为准。也就是说,比如之前做出的一些删除标记,那时候没有真的删除文件,但是经过export之后,镜像的大小就是删掉了那些文件之后的大小了。通过export成文件再导入成镜像的过程也可以精简镜像的大小。
■ Docker安装
我们一般把Docker安装在Linux上。Docker需要内核版本在3.8以上的x64主机上,如果以CentOS发行版本来计的话那么也就是说需要版本在7.0以上。下面就以在CentOS7.2上安装Docker为例
首先检查redhat的内核版本 uname -a
然后确认存在device-mapper,位于/sys/class/misc/device-mapper
正式安装docker只需要 yum install -y docker这么一句话就好了。yum果然神奇
安装完成之后 systemctl start docker来启动docker。需要注意的是,运行docker以及做一些docker的操作,因为涉及到一些普通用户无法完成的操作比如挂载文件系统,所以必须要让root权限用户来执行。
验证docker是否安装并启动成功可以键入 docker info命令来看是否有信息出现。
■ 容器与相关命令操作
容器其实是基于镜像的,所以一般得先讲镜像再讲容器。不过上面也说了,容器很像虚拟机,比较好理解。所以涉及镜像的部分就姑且默认它是存在且可运行的,我们只关注容器怎么玩。
上面提到的docker info命令,其实docker的命令都是长这样的。docker的命令清单可以通过docker help来获取,而每个分命令的具体信息比如支持哪些参数等等可以通过docker help xxx来获取。下面说几个跟容器相关的常用命令。
● docker ps
docker ps列出容器列表,给出容器信息。分成如下几个字段:
容器ID 一个uuid,可作为容器的唯一标识。一般标识一个容器有三种方法,短uuid,长uuid以及容器名称
镜像 指出这个容器基于哪个镜像
命令 容器最后执行的命令。一般可以认为是容器创建时指定的命令。
创建时间 docker会比较友好地提示“几秒前”,“几小时前”等等
状态 容器状态分成Up和Exited两种,在docker ps后加上-a参数显示所有包括Exited的容器,docker ps仅显示Up的容器
端口映射 容器比较好的一点就是它可以把自己的一个端口映射到主机的端口上,这样外界访问主机特定端口就相当于访问docker的端口了
容器名称 一个容器名称,创建容器若未指定名称则docker会自动分配一个脑洞很大的名称
除了-a之外,docker ps也有诸如-l(列出最后一个运行的容器),-n int(列出最近n个容器)等参数。
● docker run
书上第一个docker run是这样的:
docker run -t -i ubuntu /bin/bash
-t参数表示为启动的容器分配一个终端tty,-i表示将容器的stdin转移到主机的stdin上。有了这两个命令我们就可以在开启容器的同时在宿主机界面上创建一个和容器交互的界面了。界面具体采用什么shell的命令来互动就看最后的参数/bin/bash。这里的/bin/bash其实是指容器中的/bin/bash,即启动容器后启动此程序。又因为我们打开了stdin,所以在宿主机上的输入会定向到容器的stdin,以实现交互。
(ti两个参数缺一不可,如果只有-t,那么我们会看到一个命令提示符,但是无论输入什么都不回应;如果只有-i就没有命令提示符,但是会对命令输入有回应。另外一些只有在bash中使用的命令比如ll就无法使用了)
至于ubuntu,这个位置是写镜像的,现在我们本地还没有一个镜像,所以写ubuntu的默认行为是程序会到Docker Hub Registry上去下载官方的名为ubuntu的镜像,基于此镜像进行启动容器。
除了t和i两个参数外,docker run还可以用类似于--name <name>的参数来指定一个新启动的容器的名称。
-d参数可以让容器后台运行,在构建SaaS服务,或者容器中存放的是一个服务器之类的场景下用-d参数非常常见。
docker run还有一个--restart参数,这个参数的作用是让docker容器在非正常停止运行时自动重启。它会检查容器退出时的退出码,来进一步决定要不要重启,所以正常的退出时不会引起自动重启的。另外restart还可以接上一个名为on-failure的值。比如--restart=on-failure:5意思就是最多只自动重启5次。
(注意区分docker run是从镜像启动容器,而docker start是从暂停的容器启动容器)
● docker stop/start/restart
正如上面所说,容器有Up和Exited两种状态,而docker start和docker stop就是切换这两种状态用的,用法是docker start/stop <容器标识符>.。restart是重启
● docker rm
用于删除一个镜像,同样也需要在后面跟上一个容器标识符
● docker create
创建一个容器,但是不启动它。参数分配和docker run类似。总的来说应该是create+start = run。设计这个命令的意义在于使我们可以更细粒度地控制docker工作流
● docker attach
和刚才所说的docker run后面用/bin/bash程序打开类似,docker attach <容器标识符>附着到指定的,运行中的容器的stdin上,打开一个会话。不过需要注意的这个运行中的容器会随着会话的exit而被stop。如果不希望这么做,应该诉求其他方法来接入容器打开shell对话。
● docker exec
这个就是一个上面问题的解决方案。docker exec支持宿主机在宿主机层面输入一些命令,然后在容器中执行这些命令。常见的参数有-t、-d、-i、-u等等。
docker exec -d <容器标识符> <命令>。t和d两个参数是相反的,-t是tty,容器中命令返回的内容会以tty的形式反馈到宿主机的STDOUT中,而-d是daemon,命令将以背景运行的方式在容器中执行。-i也是打开STDIN,就是说宿主机可以键入内容和容器中命令执行结果进行交互。-u后面接用户名或uid,表名用一个特定的用户在容器中执行这个命令
● docker logs
上面说docker run -d可以后台地启动一个容器,那么如何取获取容器中的STDOUT输出呢?可以采用docker logs <容器标识符>这样的做法。
● docker top
docker top <容器标识符>命令可以帮助我们知道当前容器中运行着哪些进程。需要注意的是,这个top和linux中的top命令不同,不是动态的
● docker stats
docker stats <容器标识符> 上面说dockertop不是动态的,那么docker stats就是一个动态的容器信息显示。显示的信息包括CPU,内存,IO等基本系统信息参数
● docker inspect
上面说docker ps可以查看容器的信息,但是信息很有限,只有五六个字段。更加详细的信息可以通过docker inspect <容器标识符>这样的方法来获取。获得的数据是JSON格式的。另外这个命令也可以加上--format参数来规定输出的格式。
上面也提到过这个format参数,但是没有详细说明format后面应该加什么样的值。现在我们可以通过docker inspect的返回JSON来一窥这个format字符串的格式(更加详细的格式说明应该要学习go语言的模板这块内容)
比如docker inspect a_container得到的是这样一串JSON的话(当然实际上信息要多得多,这里省略大部分)
{ "Id" : "123123123", "State" : { "Status" : "running", "StartedAt" : "2017-09-30T08:46:51.696893603Z" }, "Network" : { "IpAddress" : "172.21.0.1" } }
那么我们的format参数就可以写--format='{{.Id}}'、--format='{{.State.Status}}'这样的格式,这样docker inspect --format='格式串' <容器标识符>得到的结果就是很短的字符串了比如123123123和running。
● docker rm
删除容器。比如docker rm `docker ps -a -q`就可以删除当前docker中的所有容器。
■ 镜像与相关操作
用官方一点的话来说,docker镜像是个多个文件系统叠加的只读文件系统。和传统的linux系统进行比较的话,传统linux加载文件系统时首先以只读状态加载并且做完整性检查。当检查完毕后,再将这个文件系统转化为读写。而镜像的高明之处在于,1. 镜像其本质是个文件系统,且这个文件系统永远是只读的,要变成读写只要将其打造成一个容器,而不是直接将其变成读写的文件系统。2. docker采用联合加载技术,可以把多个文件系统包装成看似只有一个文件系统,体现在镜像上,这件事就是指基础镜像和很多其他的拓展镜像之间的关系(拓展镜像就是基础镜像这个文件系统再加上其他一些附属模块的文件系统)。
这种需要写时,将只读文件系统复制成一个副本操作的术语是“写时复制”,一个处于最顶端的读写文件系统加上只读文件系统中的一些配置就是一个容器的本体了。
下面是一些跟镜像相关的常用命令:
● docker images
列出所有镜像的一些简单信息,包括字段有镜像仓库、标签、ID、创建时间、镜像大小。本地的镜像都保存在/usr/lib/docker目录下。另外每个镜像还都保存在相关的存储驱动目录下,比如/usr/lib64/device-mappepr。
回忆一下之前在docker run的时候发生的事,是从镜像仓库中下载了相关的镜像。其实具体来说,仓库是放在registry中的,而默认的registry是由Docker官方运营的Docker Hub。docker registry代码开源,所以我们也可以根据自己的需要自己搭建docker registry。熟悉github的话可以类比,一个repository中往往存放一个种类的镜像。它们之间有不同的版本。而一个registry中有多个repository。
在官方的Docker Hub中,有两种类型的repo,一种是私人repo,另一种是顶层repo。私人repo中都是用户自己创建的镜像,而顶层repo是Docker官方维护的镜像仓库,有一些常用的镜像。对于私人repo而言,其的命名由用户名和仓库名共同组成,比如username/reponame。与之相对的,顶层repo命名只包括仓库名称,比如官方维护的ubuntu仓库。关于仓库的一些其他信息和操作,后面还会陆续提到。
● docker pull
docker run指定通过一个特定镜像启动成一个容器,如果指定的镜像没能在本地找到,那么就会先从Docker Hub下载这个镜像。如果没有指明镜像的标签,那么就默认下载标签是latest的镜像。如果只想下载一个镜像而不想启动它的话就可以运行docker pull命令。比如docker pull jdeathe/centos-ssh就是下载由jedeathe用户创建的centos-ssh镜像,且tag选择是latest。
● docker search
docker search顾名思义就是搜索相关的镜像。比如docker search puppet可以搜索所有名字里带puppet的镜像,返回一个列表,表的字段是仓库名、镜像描述、用户评价(Stars,越高越好)、是否官方、是否自动构建:
● 创建镜像、docker commit和docker build
这两个命令都是用于在本地构建镜像。两个构建来源不同。docker commit是把运行中 的一个容器给冻结成一个镜像,docker build则是通过Dockerfile以及本地的文件和现有镜像等信息来创建一个新镜像。一般而言,我们很少从零开始创建一个镜像。最低也是从类似于ubuntu,centos这些基础系统镜像来创建一些个性化的镜像。
在正式使用两个命令之前,有必要说明一下Docker Hub的使用,因为这关系到我们创建出来的镜像的发布和管理。
https://hub.docker.com上可以注册新账号,在账号中可以创建新的私人repo。
在命令行界面,可以docker login来登录到Docker Hub,与之相对docker logout退出Docker Hub。登录成功后,登录信息会被保存到$HOME/.docker/config.json中。
我们启动一个容器,在容器中做了一些变更,然后就可以docker commit <容器ID> <镜像名称/标签等标识符> 来把容器打成一个镜像。
虽然commit可以制作镜像,但是不推荐这么做,更多的时候还是应该用docker build来完成。docker build是基于Dockerfile完成的,所谓Dockerfile有点类似于一个创建镜像的配置文件,指出了在创建镜像过程中应该做些什么。按照书上的做法我们一步步来看。
首先应该为Dockerfile创建一个专门的目录作为其工作空间。这个工作空间也被称为构建上下文,在构建镜像期间,Docker守护进程会访问这个上下文空间,从而使得新镜像可以得到用户想要放进去的文件、代码等等。
一个Dockerfile应该像下面这样:
# Version 0.0.1 FROM ubuntu:14.04 MAINTAINER Takanashi Kazuya "Takanashi@addr.com" RUN apt-get update && apt-get install -y nginx RUN echo 'Hello,World' > /usr/share/ngxin/html/index.html EXPOSE 80
Dockerfile的结构就是一些指令的集合。注释用#号表示,每个指令的开头都是一个关键字,必须为大写,然后后面跟一些参数,所有指令按顺序从上倒下依次执行。因为一定要有一个基础镜像,所以FROM指令往往是放在最上的。在上面这个Dockerfile被执行的过程中,Docker大概干了下面这些事情:
从基础镜像中启动一个容器
执行一条指令,对容器进行修改
执行类似docker commit操作,提交一个新的镜像层
docker基于这个镜像运行一个新容器,继续执行下一条指令
从上面的说明中不难看出,Dockerfile的执行是有断点的,即使执行到某条指令时失败了,用户仍会得到一个镜像,运行这个镜像可以看出是哪条指令失败了,用于调试。而且本次成功部分的创建会被存进缓存当中,如果下一次用户继续使用同样的DOckerfile进行build的话那么将直接从断点开始继续build。如果想要摆脱缓存,每次都从头开始那么可以在docker build后加上--no-cache参数。
分析上面的Dockerfile,FROM就是指出了及基础镜像是啥,MAINTAINER表示这个镜像由什么人创建,RUN用于执行一个命令,上面的这种调用RUN的方法默认是用/bin/sh -c运行,如果不想用它则可以RUN ["/usr/bin/apt-get","install","-y","nginx"]这样的形式来运行。EXPOSE指令指出向外部公开端口,这个指令也常用于构建容器网络,即让容器之间互相通信。
然后可以通过类似于docker build -t "new_image:tag" .的方式来创建镜像。注意后面这个点其实是指出了Dockerfile所在目录,也就是说这样的命令需要在工作空间中执行。如果需要引用远程的Dockerfile那么可以加上-f参数如docker build -t "new_image:tag" -f /tmp/Dockerfile
其他一些Dockerfile的指令还有:
CMD 后面跟一些命令参数,这个指令用于指定镜像创建后要启动成容器时要运行的命令。相当于通过CMD创建的镜像的话每次docker run的时候末尾都会自带这个命令。示例:CMD ["/usr/bin/echo","Hello"]。如果设置了CMD之后在docker run的时候又添加了参数,那么最终以后者为准。也就是说启动容器时的命令是会有覆盖现象的。
ENTRYPOINT 这个指令和CMD很像。只不过它是必须要和docker run后面的命令相配合的。比如原来CMD后面接上了["/usr/share/nginx","-g","-no-daemon"]的话,那么写ENTRYPOINT ["/usr/share/nginx"]然后在docker run 的时候后面加上-g -no-daemon的效果是一样的。也就是说ENTRYPOINT只指出了部分命令,然后靠docker run后面的参数补全命令。这样可以实现更灵活的启动方式
WORKDIR 后面加上一个容器中的目录比如WORKDIR /opt/webapp,使得CMD,ENTRYPOINT以及docker run后面的命令都在这个目录下运行。docker run的时候可以加-w参数来指定工作目录,同样会覆盖WORKDIR指令。
ENV 为容器设置环境变量,如ENV RVM_PATH /home/rvm,也可以通过ENV VAR1="value1" VAR2="value2"来设置多个环境变量。设置完成的环境变量可以在下面的指令中使用,比如WORKDIR $RVM_HOME这样子。在docker run中可以加-e "VAR=value"来指定环境变量,但是通过-e指定的环境变量是只在运行时有效的,而ENV指令设置的是在容器生命周期中始终有效的。
USER 指定该镜像以哪个用户来运行。默认是root,可以有USER username:group,USER uid等多种方式指定。
VOLUMN 向基于该镜像创建的容器增加卷。一个卷是指可以存在于一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,提供容器间的数据共享和持久化的功能。因为其本质是一个目录,所以VOLUMN指令的用法是VOLUMN ["/opt/project"]这样子。这样就会把所有基于此镜像创建的容器增加一个名为/opt/project的挂载点。之所以是个列表,是因为可以一次性添加多个卷。回过头看看docker cp就可以发现,VOLUMN和docker cp似乎是反向的两个东西。前者用于给容器添加一些内容,后者是从容器中复制一些内容出来。
COPY COPY用于给容器中复制文件,其用法是类似于COPY conf.d/ /etc/apache2/,其意思就是说把本地的conf.d目录全部复制到容器的/etc/apache2中去。COPY必须也只能处理构建上下文中的内容,不能复制上下文目录以外的内容。另外,代表容器中目录的第二个参数如果是路径那必须是绝对路径。如果这个路径中有不存在的部分,那么会自动创建,默认权限为755并且是以root用户权限创建的。第二个参数如果是以/结尾表示其是一个路径,则将第一个参数(如果是路径则是该本地路径下所有内容)的文件复制到对应容器中路径中。和COPY类似的指令还有ADD,基本上和COPY相同。唯一不同的是,ADD可以自动识别拷贝过去的文件格式,如果是压缩包格式则会自动解压再复制进去。
LABEL LABEL可以在创建镜像的过程中给镜像打标签。这个标签不同于tag,而是一个要通过docker inspect来查看的(在"Labels"字段中)。在Dockerfile中可以通过LABEL label="value" label2="value2"的形式来设置LABEL。
ARG 允许在build过程中指定一些变量,比如ARG weapp_user=user这样的形式,类似其他很多参数,可以在docker build的时候加上--build-arg参数来指定。
ONBUILD 这个指令后面可以跟一个完整的指令操作。在本镜像被构建的过程中这个指令并不执行而是给镜像添加一个触发器。当想要以本镜像为基础镜像构建新镜像时,后面跟着的这个指令就会被触发执行。
● 关于使用dockerfile的一些小提示
由于dockerfile中每一个指令都会创建一个新镜像层,同时其目的又是构建出一个镜像,通常会有很多最终需要删除的中间文件生成(比如编译安装一个软件时的编译文件),删除这些文件的指令如果单独地写成一条,那么对最终精简镜像大小没有贡献的。因为此时待删除的文件已经在之前的镜像层中被固定了下来,现在再去删除只不过是标记而已。
好的办法是将删除的命令和生成这些文件的命令写在同一个指令中。在进行删除的时候这些文件还没有进入镜像层固定下来,这就可以让我们真真正正地删除这些文件了。
● docker history
后面跟一个镜像表示符,用于查看这个镜像构建的具体过程
● docker run的-p参数和docker port
-p是port,即做端口开放/映射之用。当在docker run -p 80的时候,docker会在宿主机的大号端口中随机选择一个映射到容器的80端口上。如果是-p 8001:80那么会把宿主机的8001端口映射到容器的80端口上。
用docker port <容器标识> <端口号>可以查询出某容器某端口在宿主机上是几号端口。这些信息也可以在docker ps中综合地看到。
其实-p后面除了简单的写端口号还可以写完整socket,比如-p 127.0.0.1:80:80的话就表明所有通过localhost访问的80端口都被映射到容器的80端口去了。
● docker push
建立完镜像之后就可以通过docker push命令来讲镜像推送到Docker Hub上面。后面接的参数可以是一个镜像名,也可以是repo名(记住一个repo是一系列版本不同的同类镜像的仓库)。当是仓库名的时候要注意带个人ID,比如docker push test是报错的,因为Docker不允许推镜像到root仓库(官方仓库),而应该docker push franknihao/test 这就是推到个人的仓库就是允许的了。
● docker rmi
删除镜像
● 建立本地的私人registry
上面说过,Docker开放了docker registry的源码,所以我们可以构建自己本地的registry从而得到私密性很好的镜像仓库。建立一个本地私人registry也非常简单,Docker自产自销地把registry做成一个容器,只要run这个容器,我们就可以轻松得得到一个registry了。本地的regsitry目前还没有图形界面,只支持字符操作。
建立一个私人registry很简单:docker run -p 5000:5000 registry:2。registry是官方维护的镜像,现在最新版本是2,这个命令会从官方Hub下载镜像,并且以5000:5000的端口映射运行registry容器。运行起来之后,就可以通过docker push <hostname>:<port>/<repo_name>的方式把镜像推送到我们自己搭建的registry上面去了。
■ Docker网络
每个启动的Docker容器都会被分配到一个内网IP,和虚拟机类似的,docker的网络分割方式分成若干种。默认情况下使用host-only的方式为容器添加一个网络配置。
这个网络配置可以通过宿主机的网卡和外界进行数据的交换(在容器中ping www.baidu.com是通的)
■ 各个概念间简图
以上