Dockerfile-为容器构建Docker镜像

时间:2020-03-21 11:42:21  来源:igfitidea点击:

正如我在之前的教程中提到的那样,Docker镜像是我们容器的源代码。
图像是容器的主要组成部分。
在本教程中,我们将学习有关构建自己的Docker镜像的信息。

到目前为止,在所有教程中,我们都使用了可从Dockerhub获得的现成镜像。
但是Docker的真正用例将是在内部使用我们所需的应用程序和配置来构建镜像。
我们可以将Dockerhub中的现有镜像用作我们自己镜像的基础,但是必须在其之上添加我们自己所需的配置和软件包以及其他所需的组件。

Dockerfile只是构建Docker镜像的源代码。

我们还可以使用简单的docker commit方法来构建docker镜像,在该方法中,我们可以简单地从任何现有镜像启动容器,通过在容器中执行命令,然后在容器中执行命令,然后使用新标签进行提交,对正在运行的容器进行所需的更改。

但是,建议不要使用提交容器和构建镜像的方法来构建docker镜像。
这是因为,Dockerfile为我们提供了随时构建容器的优势,而无需我们自己做。
Dockerfile还提供了一种在构建和部署管道中自动制作容器的方法。

创建我们的第一个Dockerfile

让我们创建一个名为“ docker-file”的目录,并在该目录中创建我们的Dockerfile(这仅仅是为了轻松构建镜像。

spillai@docker-workstation:~$mkdir docker-file
spillai@docker-workstation:~$cd docker-file/

现在让我们创建一个Dockerfile

spillai@docker-workstation:~/docker-file$touch Dockerfile

现在,让我们在Dockerfile中编写一些指令,以便我们可以创建镜像。
下面显示了一个示例指令集。

spillai@docker-workstation:~/docker-file$cat Dockerfile
FROM ubuntu:12.04
MAINTAINER Sarath "[email protected]"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Our first Docker image for Nginx' > /usr/share/nginx/html/index.html
EXPOSE 80
spillai@docker-workstation:~/docker-file$

如果我们仔细查看我们的Dockerfile的内容,它不过是带有不同自变量的指令集。

指令应采用大写格式(例如,请参见“运行”,“曝光”,“发件人”,“维护者”等)。
Dockerfile中的每条指令都按照我们定义的完全相同的顺序进行处理。
Dockerfile中的每条指令集都会在镜像中添加一个添加层,然后进行提交。

现在,让我们从上面的Dockerfile创建我们的镜像。

spillai@docker-workstation:~/docker-file$sudo docker build -t="spillai/test_nginx_image" .
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
 ---> 9cbaf023786c
Step 1 : MAINTAINER Sarath "[email protected]"
 ---> Using cache
 ---> 912452fdbe6d
Step 2 : RUN apt-get update
 ---> Using cache
 ---> d711127e4d76
Step 3 : RUN apt-get install -y nginx
 ---> Running in 4fab72b24686
Processing triggers for libc-bin (2.19-0ubuntu6.3) ...
Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...
 ---> b9f58e96b137
Removing intermediate container 4fab72b24686
Step 4 : RUN echo 'Our first Docker image for Nginx' > /usr/share/nginx/html/index.html
 ---> Running in 1d1702c4dae4
 ---> c46b140fd8ad
Removing intermediate container 1d1702c4dae4
Step 5 : EXPOSE 80
www.westpalmbeachstaffingagency.com//payday-loans-mountain-home-id
 ---> Running in a98f7685870a
 ---> 728d805bd6d0
Removing intermediate container a98f7685870a
Successfully built 728d805bd6d0

首先,让我们看一下用于创建新镜像的build命令。
-t =“ spillai/test_nginx_image”标签是图像。
基本上,它的说法是该镜像是存储库溢溢的一部分,镜像名称为test_nginx_image(此时不必为此担心。
因为我们可以在构建镜像后的任何时候随时更改标记。

请注意“.”在#docker build命令的末尾说,应在当前目录中执行构建操作。

Dockerfile和构建的工作流程非常简单。
像这样...

  • Docker首先运行的是我们提到我们的FROM容器(在本例中为ubuntu:12.04)。因此,docker首先拉下该镜像,然后运行它。
  • 然后,它会在那之后寻找第一个指令,执行完之后,它会执行与#docker commit类似的操作,因此它会保存一层直到执行该指令为止的操作。
  • Docker运行的是带有最后提交的镜像的新容器(即,从Dockerfile中的上一个指令步骤创建的镜像。在上例中显示的build命令期间,消息中的Running in 1d1702c4dae4类型的消息表明了这一点。)
  • 然后,它在Dockerfile中执行下一条指令,然后再次提交并创建另一个镜像。以便在提交的新镜像中执行下一条指令。此过程一直持续到Dockerfile中的最后一条指令为止。

因此,基本上,如果我们在Dockerfile中执行的一条指令未能成功完成,我们仍然会拥有可用的镜像(该镜像是在之前的指令期间创建的)。

这对于排除故障以弄清指令失败的原因非常有帮助。
即:我们可以简单地从构建操作期间创建的最后一个镜像启动容器,并通过手动执行Dockerfile来调试失败的指令。

spillai@docker-workstation:~/docker-file$sudo docker images | grep nginx
spillai/test_nginx_image                                                 latest                                                 728d805bd6d0        14 minutes ago      232.1 MB
spillai@docker-workstation:~/docker-file$sudo docker history 728d805bd6d0
IMAGE               CREATED             CREATED BY                                      SIZE
728d805bd6d0        14 minutes ago      /bin/sh -c #(nop) EXPOSE map[80/tcp:{}]         0 B
c46b140fd8ad        14 minutes ago      /bin/sh -c echo 'Our first Docker image for N   33 B
b9f58e96b137        14 minutes ago      /bin/sh -c apt-get install -y nginx             18.1 MB
d711127e4d76        18 minutes ago      /bin/sh -c apt-get update                       21.19 MB
912452fdbe6d        18 minutes ago      /bin/sh -c #(nop) MAINTAINER Sarath "sarath@s   0 B

上面显示的#docker history命令有助于查看容器的构建方式。
它显示了在Dockerfile中的每个指令期间创建的每个镜像ID,以及执行以创建所有中间镜像以从中创建最终镜像的命令。
docker history命令所需的参数仅是镜像ID(在我们的示例中为我们刚刚创建的test_nginx_image)。

现在,让我们将新创建的nginx测试镜像作为容器运行。
可以如下所示进行。

spillai@docker-workstation:~/docker-file$sudo docker run -d -p 80:80 --name my_nginx_test_container spillai/test_nginx_image nginx -g "daemon off;"
356389b43b02c5afb55de8145cb33a3e6539c671a97c2c6974a6308f1d7bac8d

在上面的命令中,我们从新创建的镜像中启动了一个容器,其容器名称为my_nginx_test_container。
参数scrapai/test_nginx_image是图像名称(我们也可以使用图像ID),最后是此nginx -g“ daemon off;”。
是容器命令(它将启动nginx,并且容器将一直运行,直到该nginx进程正在运行)。
-p 80:80选项会将主机端口80绑定到容器端口80。
因此,我们只需访问Docker主机的IP地址,就可以看到我们的默认网页“我们的第一个Nginx Docker镜像”。

我们为docker run命令提供的最后一个参数是需要在容器内执行的命令,在我们的示例中是nginx进程。
但是,将该命令交给docker run并不是正确的方法。
我们还应该将其包含在Dockerfile中,以便在使用镜像运行容器时启动我们的nginx进程。

如下所示,这是通过在Dockerfile中使用CMD参数实现的。
让我们在Dockerfile中添加一个称为CMD的新参数。

spillai@docker-workstation:~/docker-file$cat Dockerfile
FROM ubuntu:14.04
MAINTAINER Sarath "[email protected]"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Our first Docker image for Nginx' > /usr/share/nginx/html/index.html
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
EXPOSE 80

Dockerfile中的上述CMD参数与在#docker run命令期间使用的参数完全相同。
容器启动后,这将立即启动nginx进程。
现在再次建立形象。

请注意,我们需要使用#docker rmi <image id>删除现有镜像,或者使用新标签构建此新镜像,如下所示。

spillai@docker-workstation:~/docker-file$sudo docker build -t="spillai/test_nginx_image:1.0" .

我们为修改后的图像使用了一个新标签,以避免与现有图像冲突。
我们的图像现在称为溢价/test_nginx_image:1.0(1.0是我用作示例的版本标记)。
现在,让我们使用此新镜像运行一个容器。

spillai@docker-workstation:~/docker-file$sudo docker run -d -p 80:80 --name my_nginx_test_container spillai/test_nginx_image:1.0

如上所示,我们这次并未在docker run期间使用该命令来运行nginx(但是nginx仍会自动启动,因为我们在镜像构建过程中使用Dockerfile中的CMD参数提供了该部分)。
请不要忘记在运行上述运行命令之前停止并删除我们的旧容器my_nginx_test_container(带有#docker stop my_nginx_test_container和#docker rm my_nginx_test_container),否则docker会抱怨该名称的容器已经存在。

如果在#docker运行期间仍然给出命令,将会发生什么?

但是,如果在#docker运行期间我们仍然给出命令,则docker将忽略镜像中的CMD,并将执行我们在运行期间给出的命令。

如果我在Dockerfile中输入多个CMD参数会怎样?

好吧,Docker只在Docker文件中接受一个CMD。
如果Dockerfile中有多个CMD,则Docker将使用Dockerfile中的最后一个CMD。

那么,如果我想使用Dockerfile CMD在容器中运行多个进程怎么办?

在这种情况下,我们不能对所有必需的命令使用CMD。
然后,我们应该使用诸如Supervisor之类的管理工具来管理docker容器内的多个进程。
一旦配置了主管,就可以将CMD用于主管流程(这样,主管实习生将负责处理容器内的所有必需流程)

我们已经看到#docker run命令可以覆盖在Dockerfile中提供给CMD的任何命令。
如果我们需要使用#docker run无法覆盖的内容,那么我们将不得不在Dockerfile中使用一个名为“ ENTRYPOINT”的新参数,而不是CMD。

使用ENTRYPOINT而不是CMD的另一个优点是。
使用ENTRYPOINT,我们在#docker运行期间传递的命令将作为添加参数传递给我们在ENTRYPOINT中指定的命令。
因此,我们的带有ENTRYPOINT而不是CMD的Dockerfile现在看起来像这样。

spillai@docker-workstation:~/docker-file$cat Dockerfile
FROM ubuntu:14.04
MAINTAINER Sarath "[email protected]"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Our first Docker image for Nginx' > /usr/share/nginx/html/index.html
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
EXPOSE 80

现在像以前一样使用#docker build命令创建一个新镜像,使用一个新标签来避免冲突(例如,例如splitai/test_nginx_image:2.0),然后停止并删除现有容器my_nginx_test_container并使用#docker启动一个新容器运行(将我们新创建的图像与ENTRYPOINT结合使用)

实际上,我们可以使用--entrypoint标志在运行命令期间甚至强行覆盖Dockerfile中定义的ENTRYPOINT。

一些有用的Dockerfile参数

ENV

它用于在Dockerfile内为使用该镜像启动的容器设置环境变量。
ENV的格式很简单,并且接受键/值对。

ENV MY_PATH /opt

或者,我们也可以如下定义它。

ENV MY_PATH=/opt

我们放置在ENV参数下面的后续运行指令现在可以使用该变量。
我们还可以在docker run命令期间使用-e标志(多个环境变量使用多个-e标志)传递所需的环境变量。

添加

Dockerfile中的此ADD指令用于在镜像内的Docker构建环境中添加文件和目录。
假设我们需要从本地目录(运行docker build命令的位置)复制示例nginx容器中的index.html,如下所示。

因此,现在我们可以用以下内容替换掉我们的nginx Dockerfile了。

FROM ubuntu:14.04
MAINTAINER Sarath "[email protected]"
RUN apt-get update
RUN apt-get install -y nginx
ADD index.html /usr/share/nginx/html/index.html
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
EXPOSE 80

如果我们看到上面的Dockerfile,我们现在在index.html页面上使用ADD代替了旧的echo重定向语句。
这会将index.html复制到当前目录(从中将运行带有“.”的docker build命令的目录复制到/usr/share/nginx/html/index.html)。

ADD具有非常简单的格式。
添加<来源> <目标>

源可以是URL,文件甚至是目录。
主要要求是,它应位于正确的构建位置内。
这是因为在使用Dockerfile构建镜像的第一步期间,构建上下文会传递给docker。

ADD http://apache.petsads.us/tomcat/tomcat-8/v8.0.21/bin/apache-tomcat-8.0.21.tar.gz /opt/apache-tomcat-8.0.21.tar.gz

如上所示,ADD还可以用于从URL下载文件并将其放置在图像中。

ADD的一项很酷的功能是自动解压缩压缩文件。

ADD apache-tomcat-8.0.21.tar.gz /opt/tomcat/

上面的代码将apache-tomcat-8.0.21.tar.gz(从本地构建目录)解压缩到容器内的目录/opt/tomcat中。

如果源是URL,则此功能将不起作用。
另一件事要注意的是,如果目标中存在一个具有相同名称的文件/目录,则该文件/目录不会被覆盖。
同样,如果目标目的地不存在,Docker将创建它。
提取的文件将具有0755权限集。

复制

COPY与ADD非常相似。
唯一的主要区别是COPY主要用于将文件(发生docker构建的位置)复制到图像。
而且它没有ADD可用的解压缩功能。

COPY conf//usr/share/nginx/html/

上面的代码只是将conf目录(当然是从构建上下文)中的文件复制到/usr/share/nginx/html目录。

如果源恰好是目录。
整个目录将使用完全相同的元数据复制到镜像。
另请注意,使用COPY创建的文件和目录的GID和UID均为0。

体积

该指令主要用于将卷添加到使用镜像启动的容器中(或者简单地将其放置..它会添加安装点)。
这是用于持久数据的一种非常好的方法。
如我们所知,容器在数据方面没有任何持久性。
并且容器无法访问另一个容器内的数据。
使用此指令,我们可以为具有以下属性的容器创建卷。

  • 多个容器可以访问该卷
  • 一定数量的已停止容器也可以由另一个容器访问。
  • 这样创建的卷将持续存在,直到任何一个容器使用/引用它(甚至是停止的容器)。
  • 另外,在更新图像时,更改音量也不会成为图像的一部分。

这是一个非常不错的功能,因为我们可以将诸如db,源代码之类的内容放置在卷中,而不必将其放在镜像的那一部分。
主要的添加优点是其他容器也可以使用此体积。

Dockerfile中VOLUME的语法很简单,如下所示。

VOLUME ["/opt/data"]

上面将在容器中添加一个卷/opt/data。
我们可以添加多个卷,如下所示。

VOLUME ["/opt/mydata", "/data" ]

此VOLUME指令不会告诉我们要从本地系统挂载什么。
这仅告诉docker在该卷位置放置的任何内容都不会成为镜像的一部分。
并且也可以从任何其他容器访问。

#sudo docker run -d --volumes-from data_container -p 80:80 --name my_nginx_test_container spillai/test_nginx_image

上面的命令将使my_nginx_test_container可以访问容器“ data_container”的所有卷。

我们在编写Dockerfile时总是使用FROM语句。
在某些情况下,我们需要为应用程序容器创建基础镜像。
例如,为每个环境使用一个python应用程序。
说Dev,Qa和UAT。
在这种情况下,准备基础图像并从基础图像创建特定于环境的图像始终是一件好事。

例如,假设我们创建了一个名称为spaiai/my_base_python的镜像,并且所有其他python应用程序镜像都将在FROM指令中引用该基本镜像。

因此,它将以FROM FROMai/my_base_python开头。
但是,当使用FROM FROMai/my_base_python创建任何新镜像时,我们可能需要确保执行一些预定义的env特定步骤。

这就是Dockerfile的ONBUILD指令出现的地方。

请记住,ONBUILD指令将在以后执行。
换句话说,我们需要构建任何将来的镜像(使用该镜像作为FROM的基础)来执行。

ONBUILD的语法如下所示。

ONBUILD ADD . /var/www/

ONBUILD接受任何其他Dockerfile指令作为参数。
但是,我们不能将MAINTAINER,FROM和ONBUILD本身用作ONBUILD的参数。

一旦执行FROM指令,就会执行ONBUILD指令集。
这是一件好事,因为任何其他指令可能都需要先执行ONBUILD指令。
举例来说,在新镜像中进行编译之前,我们可能需要将新的源代码复制到/opt/myapp目录。
在这种情况下,在以Dockerfile中的任何其他指令创建新镜像之前,以该镜像为基础构建新镜像时,基本镜像中的ONBUILD指令将触发。

工作目录

它是Dockefile中最简单的指令。
它为Dockerfile中所需的任何指令设置工作目录。
当然,它可以在单个Dockerfile中多次使用(因为RUN,COPY,ADD和CMD命令可能需要不同的工作目录)。

WORKDIR /opt/myapp/
RUN command arguments
WORKDIR /opt/myapp/bin
ENTRYPOINT [ "examplecommand" ]

上面显示的示例将在/opt/myapp /目录中执行RUN命令,而ENTRYPOINT将在/opt/myapp/bin目录中执行。