Docker镜像怎么构建

74次阅读
没有评论

共计 10229 个字符,预计需要花费 26 分钟才能阅读完成。

本篇内容主要讲解“Docker 镜像怎么构建”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让丸趣 TV 小编来带大家学习“Docker 镜像怎么构建”吧!

Docker 镜像介绍

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

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

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

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

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

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

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

列出镜像

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 仓库。

拉取镜像

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

也可以使用 docker pull 命令自己预先拉取镜像到本地。下面的命令拉取所有的 fedora 基础镜像:

docker pull fedora

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

docker images fedora

如果只想拉取一种,可以在镜像名后加标签,例如只拉取 Fedora 20:

docker pull fedora:20

查找镜像

可以使用 docker search 命令查找所有 Docker Hub 上公共的可用镜像:

docker search puppet

上面的命令查找了所有带 puppet 的镜像,返回信息如下:

NAME 仓库名;

DESCRIPTION 描述;

STARTS 用户评价;

OFFICIAL  是否官方;

AUTOMATED    是否由 Docker Hub 的自动构建流程创建的。

  构建镜像

构建镜像由两种方法:

使用 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 中的指令:

Docker 从集成镜像运行一个容器;

执行一条指令,对容器做出修改;

执行类型 docker commit 的操作,提交一个新的镜像层;

Docker 再基于刚刚提交的镜像运行一个新容器;

执行 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 触发器会按照在父镜像中指定的顺序执行,并且只能被继承一次(即只能在子镜像中执行,而不会再孙镜像中执行)。

  将镜像推送到 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 并输入描述就创建成功了。

  删除镜像

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 

  运行自己的 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

到此,相信大家对“Docker 镜像怎么构建”有了更深的了解,不妨来实际操作一番吧!这里是丸趣 TV 网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计10229字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)