文档章节

Docker学习笔记四 镜像

一万
 一万
发布于 2016/07/09 11:40
字数 5864
阅读 172
收藏 2

4.1 Docker镜像介绍

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

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

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

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

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

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

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

4.2 列出镜像

docker images

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

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

每个镜像仓库可以存放很多镜像,比如ubuntu仓库包含了Ubuntu12.04、12.10、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

 

© 著作权归作者所有

一万
粉丝 30
博文 102
码字总数 173386
作品 0
朝阳
程序员
私信 提问
Docker入门教程 Part 2 容器操作

前言 本篇笔记是官方Get Started入门教程的第2步 容器操作,涉及Dockerfile镜像创建,容器中运行和停止镜像,仓库中分享和拉取镜像等,涵盖了Docker容器的常见操作,是特别经典的例子,值得好...

iotisan
2017/10/23
0
0
docker学习2|在容器中部署nginx并保存、运行容器

点击上方“中兴开发者社区”,关注我们 每天读一篇一线开发者原创好文 ▎作者简介 作者朱磊是开源软件爱好者,致力于web应用部署、运维领域解决方案的研究。将web应用部署在Docker上能够极大...

o4dc8ojo7zl6
2017/12/13
0
0
Docker 学习笔记1. 安装

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 https://blog.csdn.net/xundh/article/details/91846819 Docker 学习笔记1. 安装 一、简介 ...

谢厂节
06/14
0
0
Docker 学习笔记合集第一季 —— image container 基本操作

Docker 学习笔记合集第一季 —— image container 基本操作 Docker · chenhengjie123 · 于 1 年前发布 · 最后由 sanlengjingvv 于 1 年前回复 · 2262 次阅读 本帖已被设为精华帖! 学习 ...

小祁1124
2016/11/17
27
0
Docker学习笔记(二):容器

概述:通过Docker构建、运行和发布镜像 (一)新的开发环境: 在过去的应用开发方式中,我们要同时配置开发环境和生产环境。而使用Docker,我们只需要获得一个运行环境的镜像,应用的代码、依...

u013637262
2018/05/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

HashSet和HashMap有什么区别?

HashSet 底层是采用 HashMap 实现,HashSet 的实现比较简单,HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现 调用 HashSet 的 add 方法时,实际上是向 HashSet 对象内部持有的 Ha...

ConstXiong
10分钟前
2
0
击穿JVM虚拟机

什么是JVM虚拟机 首先我们需要了解什么是虚拟机,为什么虚拟机可以实现夸平台,虚拟机在计算机中扮演一个什么样的角色。 (从下向上看) 看上图的操作系统与虚拟机层,可以看到,JVM是在操作...

兜兜毛毛
17分钟前
3
0
OpenNMS 利用 Sentinel处理Netflow(流量流向分析)

准备环境 CentOS-7-x86_64 Java8 OpenNMS 23.0.4 minion-23.0.4 sentinel-23.0.4 elasticsearch-6.7.1.tar.gz OpenNMS 配置 1 配置ActiveMQ vi $OPENNMS_HOME/etc/opennms-activemq.xml 取消......

qoswork
21分钟前
4
0
PHP Socket初探---先从一个简单的socket服务器开始

socket的中文名字叫做套接字,这种东西就是对TCP/IP的“封装”。现实中的网络实际上只有四层而已,从上至下分别是应用层、传输层、网络层、数据链路层。最常用的http协议则是属于应用层的协议...

bengozhong
28分钟前
4
0
Git

指令 git init :创建版本库,生成.git文件夹 git add XX:上传代码到暂存区 git state:查看目前本地工作起、暂存区、分支,三者之间的文件状态 git diff demo.html:查看工作区和暂存区的代码...

Hui先生
48分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部