Docker学习笔记四 镜像

4.1 Docker镜像介绍html

Docker镜像是由文件系统叠加而成。最底端是一个引导文件系统(bootfs),Docker用户几乎不会和引导文件系统有交互,当容器启动后它会被卸载而移动到内存中。nginx

第二层是root文件系统(rootfs),它位于引导文件系统之上。rootfs能够是一种或多种操做系统。rootfs永远是只读状态。git

Docker利用联合加载(union mount)技术又会在rootfs层上加载更多的只读文件系统。联合加载指的是一次同时加载多个文件系统,但在外部看起来像是一个文件系统。联合加载会将各层文件系统叠加到一块儿,这样最终的文件系统会包含全部底层的文件和目录。github

Docker将这样的文件系统成为镜像。一个镜像能够放到另外一个镜像的顶部。位于下面的镜像成为父镜像,最底部的镜像成为基础镜像。web

最后,当从一个镜像启动容器时,Docker会在该镜像的最顶层加载一个读写文件系统。在Docker中运行的程序是在这个读写层中执行的。docker

当Docker第一次启动一个容器时,初始的读写层是空的。当文件系统发生变化时,这些变化都会应用到这一层上。好比,想修改一个文件,这个文件首先会从该读写层下面的只读层复制到读写层。该文件的只读版本依然存在,可是已经被读写层中的该文件副本隐藏。shell

这种机制成为写时复制,这也是使Docker强大的技术之一。每一个只读镜像都是只读的,而且之后永远不会变化。当建立一个容器时,Docker会构建出一个镜像栈,并在栈的最顶端添加一个读写层。这个读写层再加上其下面的镜像层以及一些配置数据,就构成了一个容器。apache

4.2 列出镜像编程

docker images

本地镜像都保存在Docker宿主机的/var/lib/docker目录下,/var/lib/docker/containers目录中保存着全部的容器。ubuntu

镜像从仓库下载下来。镜像保存在仓库中,而仓库保存在Registry中。默认的Registry是由Docker公司运营的公共Registry服务,即Docker Hub。能够将镜像仓库想象为相似Git仓库的东西,它包括镜像、层以及镜像的元数据。

每一个镜像仓库能够存放不少镜像,好比ubuntu仓库包含了Ubuntu12.0四、12.十、13.04等等。使用下面的名利能够把ubuntu仓库中的镜像所有拉取到本地:

docker pull ubuntu

为了区分一个仓库中的不一样镜像,Docker提供了标签(功能 ),每一个镜像都带有一个标签。咱们能够经过在仓库名后面加上一个冒号和标签名来指定该仓库中的某一镜像:

docker run -t -i --name new_container ubuntu:12.0 /bin/bash

Docker Hub中有两种类型的仓库:用户仓库(user repository)和顶层仓库(top-level repository)。用户仓库的镜像都是由用户建立的,而顶层仓库则是由Docker官方管理的。

用户仓库的命名由用户名和仓库名两部分组成,如jamtur01/puppet。前面是用户名后面是仓库名。

与之相对,顶层仓库只包含仓库名,如ubuntu仓库。

4.3 拉取镜像

用docker run 命令从镜像启动一个容器时,若是该镜像不在本地,Docker会先从Docker Hub下载该镜像。若是没有指定具体的镜像标签,那么Docker会自动下载latest标签的镜像。

也可使用docker pull命令本身预先拉取镜像到本地。下面的命令拉取全部的fedora基础镜像:

docker pull fedora

拉取完成后只查看fedora镜像的内容:

docker images fedora

若是只想拉取一种,能够在镜像名后加标签,例如只拉取Fedora 20:

docker pull fedora:20

4.4 查找镜像

可使用docker search命令查找全部Docker Hub上公共的可用镜像:

docker search puppet

上面的命令查找了全部带puppet的镜像,返回信息以下:

  • NAME 仓库名;
  • DESCRIPTION 描述;
  • STARTS 用户评价;
  • OFFICIAL  是否官方;
  • AUTOMATED     是否由Docker Hub的自动构建流程建立的。

4.5 构建镜像

构建镜像由两种方法:

  • 使用 docker commit 命令;
  • 使用 docker build 命令和Dockerfile文件。

如今官方不推荐使用docker commit命令,而应使用更灵活、更强大的Dockerfile来构建Docker镜像。

通常来讲咱们不是真正建立新镜像,而是基于一个已有的基础镜像,如ubuntu、fedora等,构建镜像。

(1)建立并登录Docker Hub帐号

注册一个Docker Hub帐号后,登录:

docker login

成功登录后,认证信息保存到~/.dockercfg文件中,供后面使用。在个人测试环境中未找到~/.dockercfg文件。

(2) 用 docker commit 命令建立镜像

首先建立一个新容器:

docker run -i -t ubuntu /bin/bash

接下来安装Apache:

apt-get -yqq update

apt-get -y install apache2

使用exti从容器中退出,而后提交:

docker commit 容器ID ivan/apache2

命令指定了要提交修改的容器ID,以及一个目标镜像仓库名。

也能够在提交镜像时指定更多的数据(包括标签)来详细描述:

docker commit -m="A new custom image" --author="Ivan" 容器ID ivan/apache2:webserver

在这条命令里,用 -m 指定了镜像的提交信息, --author 列出镜像的做者信息,最后为该镜像添加了:webserver标签。

可使用docker inspect命令来查看镜像的详细信息:

docker inspect ivan/apache2:webserver

(3)用Dockerfile构建镜像

官方推荐使用Dockerfile定义文件和docker build 命令来构建镜像。Dockerfile使用基本的基于DSL语法(声明式编程语言)的指令来构建一个Docker镜像,以后使用docker build命令基于该Dockerfile中的指令构建一个新的镜像。

首先须要建立一个文件夹,而后在这个文件夹里建立初始的Dockerfile。

mkdir static_web
cd static_web
touch Dockerfile

上述代码建立了一个static_web的目录用来保存Dockerfile,这个目录就是构建环境,Docker称此为上下文(context)或构建上下文(build context)。Docker会在构建镜像时将构建上下文和该上下文中的文件和目录上传到Docker守护进程。这样Docker守护进程就能直接访问想在进程中存储的代码、文件或者其余数据。

下面向Dockerfile中添加内容:

# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo "Hi, I am in your container' > /user/share/nginx/html/index.html
EXPOSE 80

该Dockerfile由一系列指令和参数组成。每条指令,如FROM,都必须是大写字母且后面要跟随一个参数。Dockerfile中的指令会按顺序从上向下执行。

每条指令都会建立一个新的镜像层并对镜像进行提交。Docker大致上按照以下流程执行Dockerfile中的指令:

  1. Docker从集成镜像运行一个容器;
  2. 执行一条指令,对容器作出修改;
  3. 执行类型docker commit的操做,提交一个新的镜像层;
  4. Docker再基于刚刚提交的镜像运行一个新容器;
  5. 执行Dockerfile中的下一条指令,直到全部指定都执行完毕。

若是Dockerfile因为某些缘由没有正常结束(如某条指令失败了),那么将获得一个可使用的镜像。这对调试颇有帮助:能够基于该镜像运行一个具有交互功能的容器,使用最后建立的镜像对为何指令会失败进行调试。

Dockerfile也支持注释,以 # 开发的行都会被认为是注释。

每一个Dockerfile的第一条指令都应该是FROM。FROM指令指定一个已经存在的镜像,后续指令都将基于这个镜像运行,这个镜像称为基础镜像。

接着写入了MAINTAINER指令,这条指令会告诉Docker该镜像的做者以及做者的邮件地址。

在这以后指定了三条RUN指令。RUN指令会在当前镜像中运行指定的命令。在这个例子中,经过RUN指令更新了已经安装的APT仓库,安装nginx包,以后建立了/usr/share/nginx/html/index.html文件。

默认状况下,RUN指令会在shell里使用命令包装器/bin/sh -c来执行。若是在一个不支持shell的平台运行或者不但愿在shell中运行,也可使用exec格式的RUN指令,如:

RUN [“apt-get”, "install", "-y", "nginx"]

接着使用了EXPOSE指令,这条指令告诉Docker该容器内的应用程序将会使用容器的指定端口。注意,这并不意味着能够自动访问该端口。出于安全的缘由,Docker不会自动打开该端口,而是须要在使用docker run运行容器时来指定须要打开哪些端口。

能够指定多个EXPOSE指令来向外部公开多个端口。

(4)基于Dockerfile构建新镜像

执行 docker build 命令,Dockerfile中的全部指令都会被执行而且提交,而且在该命令成功结束后返回一个新镜像。

cd static_web
docker build -t="ivan/static_web" .

-t 参数为新镜像设置仓库,也能够在构建时为镜像设置标签。

docker build -t="ivan/static_web:v1" .

上面两个命令中的 . 告诉Docker到本地当前目录找Dockerfile文件。也能够指定一个Git仓库的源地址来指定Dockerfile的位置。

docker build -t="ivan/static_web:v1" git@github.com:ivan/docker-static_web

构建过程当中,构建上下文会被上传到Docker守护进程。若是在构建上下文的根目录中存在.dockerignore文件,那么该文件内容会被按行进行分割,每一行都是一条文件过滤匹配规则。匹配经过的文件不会被上传到Docker守护进程。该文件中模式的匹配规则采用了Go语言中的filepath。

(5)指令失败

若是指令失败会返回失败前构建的镜像,咱们能够启动容器加载镜像检查失败缘由。

(6)构建的缓存

因为每一步的构建过程都会讲结果提交为镜像,因此以前的镜像层会被看作是缓存。好比在构建的第四步出错,修改后第四步后从新构建,因为第一步到第三步未被修改,Docker会从第四步开始构建。若是第一步到第三步之间作了一些修改,Docker会从发生变化的第一条指令开始构建。

有时构建时不但愿利用缓存能够加入--no-cache标志:

docker build --no-chace -t-"inva/static_web"

(7)基于构建缓存的Dockerfile模板

通常在Dockerfile文件的开头都会使用相同的指令集模板,好比

FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
ENV REPERESHED_AT 2016-07-08
RUN apt-get -qq update

前两条指令的运行结果不会改变,第三条指令使用ENV建立了一个REFRESHED_AT环境变量,这个环境变量用来代表该镜像模板最后的更新时间。最后使用RUN指令来运行apt-get -qq update指令。该指令运行时会刷新APT包的缓存,用来确保将要安装的每一个软件包都更新到最新版本。

对于这个模板,若是想刷新一个构建,只须要修改ENV指令中的日期。

(8)查看新镜像

可使用docker images 来查看新构建的镜像。

使用docker history命令深刻了解该镜像是怎么构建的:

docker history 镜像ID

(9)重新镜像启动容器

上面的例子中构建了一个nginx镜像,如今启动它看看是否正常:

docker run -d -p 80 --name static_web ivan/static_web nginx -g "daemon off";

命令中参数 -d 表示容器以守护方式运行,nginx -g "daemon off"是须要在容器中运行的命令。

新出现的参数 -p 控制Docker在运行时应该公开哪些网络端口给宿主机。运行一个容器时,Docker能够经过两种方法来在宿主机上分配端口:

  • Docker能够在宿主机上随机选择一个位于49153~65535的一个比较大的端口号来映射到容器中的指定端口(例子中是80);
  • 能够在Docker宿主机中指定一个具体的端口号来映射到容器中的指定端口上(例子中是80)。

使用 docker ps 命令能够查看端口分配状况。

也能够经过docker port查看容器的端口映射状况:

docker port 容器ID 80

上面的名利指定了向查看映射状况的容器ID和容器端口号,返回的将是宿主机中映射的端口号。

docker run 的 -p 选项能够灵活的指定容器和宿主机之间的端口映射关系。好比指定将容器中的端口映射到Docker宿主机的某一特定端口上:

docker run -d -p 80:80 --name static_web ivan/static_web nginx -g "daemon off";

页能够将容器内的端口绑定到特定的IP的端口上:

docker run -d -p 127.0.0.1:80:80 --name static_web ivan/static_web nginx -g "daemon off";

将容器内的80端口绑定到宿主机127.0.0.1这个IP的80端口上。

也能够绑定到一个宿主机的特定IP的随机端口上:

docker run -d -p 127.0.0.1::80 --name static_web ivan/static_web nginx -g "daemon off";

使用 -P 参数,能够用来将Dockerfile中EXPOSE指令设置的端口绑定到宿主机的随机端口上:

docker run -d -P --name static_web ivan/static_web nginx -g "daemon off";

在端口绑定时使用/udp后缀来指定UPD端口绑定。

在获得宿主机的绑定IP和端口后可使用curl来测试nginx:

curl 127.0.0.1:端口号

(10)Dockerfile指令

  • CMD 

用于指定一个容器启动时要运行的命令。相似于RUN指令,可是RUN指令是在指定镜像被构建时要运行的命令,而CMD是指定容器被启动时要运行的命令。这和docker run命令启动容器指定要运行的命令是同样的。

CMD ["/bin/bash", "-l]

例子中要运行的命令存放在一个数组结构中。这将告诉Docker按指定的原样来运行该命令。也能够不使用数组,这时候Docker会在指定的命令前加上/bin/sh -c。这在执行该命令的时候可能会致使意料以外的行为,因此Docker推荐一直使用以数组语法来设置要执行的命令。

使用docker run命令时指定参数会覆盖Dockerfile中的CMD命令。

在Dockerfile中只能指定一条CMD指令。若是指定了多条,也只有最后一条CMD指令会被使用。若是想在启动容器时运行多个进程或者多条命令,能够考虑使用相似Supervisor这样的服务管理工具。

  • ENTRYPOINT

与CMD类似也是指定一些要运行的命令。若是在Dockerfile中指定了ENTRYPOINT,CMD指令或docker run指定的命令参数都会被当作参数再次传递给ENTRYPOIN指定的命令。

例如

ENTRYPOINT ["/user/bin/nginx"]

从新构建镜像后启动:

docker run -t -i ivan/static_web -g "daemon off;"

这样-g "daemon off;"就会传递给ENTRYPOINT,组成一条命令。

也能够组合使用ENTRYPOINT和CMD指令:

ENTRYPOINT ["/user/bin/nginx"]
CMD ["-h"]

此时当启动一个容器,若是指定-g "daemon off;"参数就会让Nginx守护进程之前台方式运行。若是在启动容器的时候不指定任何参数,则CMD中的-h就会传递给/user/bin/nginx,显示Nginx的帮助信息。

这使咱们能够构建一个镜像,该镜像既能够运行一个默认的命令,也支持经过docker run为该命令指定可覆盖的选项或者标志。

也能够在docker run中指定--entrypoint标志覆盖ENTRYPOINT指令。

  • WORKDIR

用来为Dockerfile中后续的一系列指令设置工做目录,也能够为最终的容器设置工做目录:

WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackup"]

例子中将工做目录切换为/opt/webapp/db后运行了bundle install命令,以后又将工做目录设置为/opt/webapp,最后设置了ENTRYPOINT。

docker run 命令可使用-w覆盖WORKDIR。

  • ENV

用来在镜像构建过程当中指定环境变量:

ENV RVM_PATH /home/rvm/

这个新环境变量能够在后续的任何RUN指令中使用。

也能够在其余指令中直接使用这些环境变量:

ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR

若是须要能够经过在环境变量前加上一个反斜线来进行转义。

这些环境变量会被持久保存到从咱们的镜像建立的任何容器中。

也可使用docker run -e来传递环境变量,但这些环境变量只会在运行时有效:

docker run -ti -e "WEB-PORT=8080" ubuntu env
  • USER

用来指定该镜像以什么用户运行,能够指定用户名或UID,组或GID,也能够是二者的组合:

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

也能够在docker run 命令中经过-u 选项来覆盖USER指定的值。

若是不指定,默认用户为root。

  • VOLUME

用来向基于镜像建立的容器添加卷。一个卷是能够存在于一个或者多个容器内的特定目录。这个目录能够绕过联合文件系统,并提供以下功能:

  • 卷能够在容器间共享和重用;
  • 一个容器能够没必要须和其余容器共享卷;
  • 对卷的修改是马上生效的;
  • 对卷的修改不会对更新镜像产生影响;
  • 卷会一直存在直到没有任何容器再使用它。
VOLUMN ["/opt/project","/data"]

这条指令将会为基于此镜像建立的任何容器建立两个挂载点。

  • ADD

用来将构建环境下的文件和目录复制到镜像中。

ADD software.lic /opt/application/software.lic

ADD指令将构建目录下的software.lic文件复制到镜像中去。

源文件的位置能够是一个URL,或者构建上下文中的文件名或目录。不能对构建目录以外的文件进行ADD操做。

在ADD文件时,Docker经过目的地址参数末尾的字符来判断文件源是目录仍是文件。若是目的地址以/结尾,那么Docker认为源位置指向的是目录;若是目的地址不以/结尾,那么Docker就认为源位置指向的是文件。

若是源文件是本地归档文件(合法的归档文件包括gzip、bzip2和xz),Docker会自动将归档文件解开:

ADD lastest.tar.gz /var/www/wordpress/

上例将归档文件解压到/var/www/wordpress/目录下。Docker解开归档文件的行为和使用带-x选项的tar命令同样:原目的目录已经存在的内容加上归档文件中的内容。若是目的位置的目录下已经存在了和归档文件同名的文件或者目录,目的位置中的文件或者目录不会被覆盖。

截止到1.0.0版本还不能解压URL方式制定的归档文件。

若是目的位置不存在,Docker将会为咱们建立这个全路径,包括路径中的任何目录。新建立的文件和目录的模式为0755,而且UID和GID是0.

ADD命令会使得构建缓存无效。

  • COPY

相似于ADD,它只复制文件而不会作提取和解压。

COPY conf.d /etc/apache2/

这条指令会将本地conf.d目录中的文件复制到/etc/apache2/目录中。

文件源路径必须是在构建目录中。

任何由该指令建立的文件或者目录的UID和GID都会设置为0.

若是目的目录不存在,Docker会自动建立全部须要的目录结构。

  • ONBUILD

为镜像添加触发器。当一个镜像被用作其余镜像的基础镜像时,该镜像中的触发器会被执行。

触发器会在构建过程当中插入新指令,能够认为这些指令是紧跟在FROM以后指定的。触发器能够是任何构建指令:

ONBUILD ADD . /app/src
ONBUILD RUN cd /app/src && make

可是FROM、MAINTAINER和ONBUILD不能用于ONBUILD指令,为了防止在Dockerfile构建过程当中产生递归调用的问题。

使用docker inspect 容器ID能够查看容器使用的镜像的ONBUILD指令信息。

ONBUILD触发器会按照在父镜像中指定的顺序执行,而且只能被继承一次(即只能在子镜像中执行,而不会再孙镜像中执行)。

4.6 将镜像推送到Docker Hub上

docker push ivan/static_web

还能够进行自动构建,只须要将Github或BitBucket中含有Dockerfile文件的仓库链接到Docker Hub便可。向这个代码仓库推送代码时,将会触发一次镜像构建活动并建立一个新镜像。

点击Dockr Hub网站右上角的Create下面的Create Automated Build,关联一个Github帐号而后在Github网站上作受权确认。成功后返回Docker Hub点击“Create Auto-build”,选择一个Github Repository并输入描述就建立成功了。

4.7 删除镜像

docker rmi ivan/static_web

从删除命令的输出能够看出Docker的分层文件系统,每一个Deleted行都表明一个镜像层被删除。

上述操做只会将本地的镜像删除。若是以前已经将该镜像推送到Docker Hub上,它在Docker Hub上将依然存在。若是想删除Docker Hub上的镜像仓库,须要登陆Docker Hub执行删除操做。

还能够指定一个镜像名列表来删除多个镜像:

docker rmi ivan/apache2 ivan/static_web

删除所有镜像的技巧:

docker rmi 'docker images -a -q'

4.8 运行本身的Docker Registry

有两种选择:

  • 利用Docker Hub上的私有仓库;
  • 运行本身的Registry。

Docker公司开源了运行Docker Registry固然代码,能够基于此运行本身的Registry。

(1)从容器中运行Registry

docker run -p 5000:5000 registry

启动一个Registry应用的容器,并绑定到宿主机的5000端口。

运行完成后在浏览器输入地址:宿主机IP:5000;看到信息:"docker-registry server",说明Registry Server启动成功了。

(2)提交镜像到本身的Registry

成功运行了Registry容器,可是没法提交,缘由是不知道主机名,本身认为的localhost是不对的。

通过测试用127.0.0.1是能够的。

首先将已有的镜像打上新的Registry标签

docker tag 镜像ID 127.0.0.1:5000/ivan/static_web

最后将镜像提交

docker push 127.0.0.1:5000/ivan/static_web
相关文章
相关标签/搜索