转载本文请联系代码综艺公众号。前言使用第三方镜像绝对不是学习Docker的最终目的。你最想要的是建立自己的镜像;将自己的程序、文件、环境等构建到您想要的应用镜像中,方便后续的部署、启动和维护;而Dockerfile就是专门干这个的。通过类似于简单编码的形式,最终可以构建自己的形象,所以一定要学习。正文1.Dockerfile简介在日常的开发过程中,需要编写相应的程序文件,最后通过编译打包生成相应的可执行文件或类库;这里的Dockerfile就像我们平时写的程序文件一样,但是内部的语法和关键字没有程序那么复杂和繁多,而是比较简单。最后可以通过dockerbuild命令将相应的程序、文件、环境等构建成镜像。在第一篇文章的最后,我简单的使用了Dockerfile构建了一个镜像。这里对Dockerfile文件有一个新的认识,如下图所示:Dockerfile是一个文本文件,但是不需要指定后缀类型;在文件内容中,FROM、WORKDIR、COPY等都是按照规则写入关键字后,就可以将指定的文件构建为镜像。build操作统一由Dockerdaemon进行,它会先对文件内容的语法进行初步的校验(如果语法不正确,会返回错误信息),然后一条一条的运行指令,每次生成一个新的镜像层,直到所有指令都执行完毕,然后build生成最终的镜像。Dockerfile与镜像、容器的关系如下:总结Dockerfile的知识点;搭建时,指令从上到下一条一条执行;每条指令都会创建一个新的图像层,每一层都是前一层变化的增量;使用#进行注释;关键字约定大写,后跟至少一个参数;2.Dockerfile关键字2.1FROM关键字指定了基础镜像,即新镜像基于哪个镜像。比如盖房子,可以从一块空地开始,也可以从别人打的基石开始,甚至可以在别人盖的毛坯房的基础上进行装修。如果你想盖房子,你可以从空地开始,或者从铺设的基石开始,或者从毛坯房开始。不管怎样,房子最终会盖好;这里需要注意的是,无论怎样,空地都是必不可少的;构建镜像也是如此。底层必须有一个基本图像。推荐使用官方镜像作为基础镜像,推荐使用Alpine类型,因为控制严格,体积小。用法如下:#FROM[--platform=][:][AS]ARGCODE_VERSION=latest#DefinevariableFROMbase:${CODE_VERSION}#指定基础镜像2.2MAINTAINER/LABELkeyMAINTAINER这个词指定了维护者的相关信息,即谁构建了构建的镜像,他的邮箱是什么;LABLE用于对图像进行标注,以键值对的形式指定,比MAINTAINER更灵活,可以使用LABLE代替MAINTAINER。用法如下:#LABEL===...LABELcom.example.version="0.0.1-beta"LABELvendor1="ACMEIncorporated"2.3RUN键构建过程中需要运行的命令,比如构建过程中需要执行一个命令下载对应的包,这里需要使用RUN关键字;用法如下:#两种命令方式均可#RUN#RUN["executable","param1","param2"]#执行命令,Linux支持的相关命令RUN/bin/bash-c'source$HOME/.bashrc;echo$HOME'RUN["/bin/bash","-c","echohello"]2.4WORKDIR关键字通常需要在启动容器时根据镜像进入容器;可以通过WORKDIR进入容器时指定目录;用法如下:WORKDIR/path#指定路径2.5ENV关键字可以在构建过程中设置环境变量;就像我们平时安装程序一样,需要配置环境变量,方便访问;ENV关键字是根据需求设置相应的环境变量;用法如下:#ENV=。..#指定环境变量ENVPATH=/usr/local/postgres-$PG_MAJOR/bin:$PATH2.6ADD关键字会将宿主机的资源复制到镜像中,自动解压,也可以从远程宿主机下载资源并复制到镜像中;用法如下:#两种命令方式均可#ADD[--chown=:]...#ADD[--chown=:]["",...""]添加https://example.com/big.tar.xz/usr/src/things/2.7COPY关键字将宿主机的资源复制到镜像中,只支持读取构建所在宿主机的资源。与ADD关键字相比,它更加透明。什么操作就是什么。用法如下:#复制资源到容器,两种命令格式都可以#COPY[--chown=:]...#COPY[--chown=:]["",...""]COPYrequirements.txt/tmp/2.8VOLUME关键字挂载数据卷,前面的常用命令有提到。要挂载数据卷,请使用VOLUME在Dockerfile中指定挂载路径。根据构建的镜像运行容器时,默认会有构建时挂载的信息。用法如下:#挂载数据卷VOLUME["/data"]VOLUME/myvol2.9EXPOSE关键字指定容器运行时对外暴露的端口;即容器根据镜像启动时,容器对外暴露端口。用法如下:#EXPOSE[/...]EXPOSE80/tcp#ExposureportEXPOSE80/udp2.10CMD关键字指定启动容器时执行的命令,只有最后一个将生效;即当容器根据镜像启动后,容器需要执行什么命令。用法如下:#两种格式都OK#CMD["param1","param2"]#CMDcommandparam1param2#执行命令统计行数、字数、字节数CMDecho"Thisisatest."|wc-#执行wc--helpcommandCMD["/usr/bin/wc","--help"]2.11ENTRYPOINT关键字指定根据镜像启动容器时要执行的命令,可以添加命令;执行时序与CMD相同。用法如下:#ENTRYPOINT["executable","param1","param2"]#ENTRYPOINTcommandparam1param2ENTRYPOINT["top","-b"]2.12ARG关键字通过ARG指令定义一个变量;和写代码时定义的变量一样,根据需要定义即可。用法如下:#ARG[=]ARGuser1=someuserARGbuildno=12.13ONBUILD关键字当基于父镜像构建新镜像时,会触发父镜像的OBUILD。3.实际演示这里我们仍然以.NetCore项目为例进行镜像搭建,其他编程语言项目也一样;这次我们将一步步弄清楚每个命令的用法。以下关于项目创建和发布的具体细节分享在第一篇末尾,小伙伴们可以参考。这里主要演示Dockerfile的关键字。3.1准备项目和Dockerfile新建一个项目,什么都不用改,使用默认界面演示即可,如下:Dockerfile内容如下:#指定基础镜像,并在此基础上构建自己的项目镜像FROMmcr.microsoft.com/dotnet/core/aspnet:3.1#指定自己的工作目录,进入容器时,目录WORKDIR/myApp#将buildcontext目录下的文件复制到容器当前工作目录下,即,/myAppCOPY..#容器对外暴露端口,项目启动端口时暴露相应端口。EXPOSE80#执行命令,这里默认是从80端口启动#类似于在Linux系统的工程目录下执行dotnetDockerfileDemo.dll。ENTRYPOINT["dotnet","DockerfileDemo.dll"]记得右击Dockerfile,选择Properties,然后将Dockerfile设置为alwayscopy,这样后续的更新和改动在发布的时候会自动复制到对应的release目录下。3.2将项目以文件的形式发布,连同Dockerfile一起拷贝到安装Docker的机器上进行构建(这里我还是用我的云服务器);泊坞窗建设-t我的形象:v1.0。解析:-t:指定镜像的名称和标签,一般格式为name:tag或name,myimage为镜像名称,v1.0为tag;-f:指定要使用的Dockerfile的路径,因为Dockerfile在当前路径下,所以不用指定;最后一点:官方名称对于构建上下文,点表示指定当前目录。指定目录下的文件会被送到dockerdaemon进行构建,所以不要指定/(斜线代表根目录,文件较多)。其他选项参数可以根据需要使用,以上比较重要。3.3根据构建好的镜像启动容器,在Dockerfile中查看命令效果;启动容器如下:ENTRYPOINT["dotnet","DockerfileDemo.dll"]这行代码相当于直接执行项目目录下的dotnetDockerfileDemo.dll没错,目的就是启动我们的项目。可以通过dockerlogs查看容器内部日志,如下:3.4丰富Dockefile文件内容,查看构建后的详细信息。文件内容如下:#指定基础镜像,在此基础上构建自己的项目镜像FROMmcr.microsoft.com/dotnet/core/aspnet:3.1#指定维护者MAINTAINERCodeZYQ<1137533407@qq.com>#把labelLABELcreatename="CodeZYQ"#指定自己的工作目录,进入容器时,目录appWORKDIR/myapp#将buildcontext目录下的文件复制到容器中COPY中的工作目录..#定义变量ARGmyPort=8080#使用环境变量方式更改启动端口,拼接使用定义的变量ENVASPNETCORE_URLS=http://+:$myPort#通过RUN执行相关命令,根据需要执行相关命令CommandRUNmkdirtestDir#挂载数据卷,这里模拟挂载日志目录VOLUME/Logs#容器对外暴露端口,项目启动时暴露相应端口EXPOSE$myPort#执行命令,这里默认是80端口启动#类似于在Linux系统的工程目录下执行dotnetDockerfileDemo.dll。ENTRYPOINT["dotnet","DockerfileDemo.dll"]执行如下命令构建新镜像:通过dockerlogs查看容器日志,如下:查看数据卷是否挂载成功,进入容器,会有多在根目录下的Logs目录。也可以通过dockerinspect容器查看容器的详细信息,如下:标签也标记成功:也可以通过dockerinspectimage查看内部镜像,具体执行命令dockerinspectnewimage如下:Dockerfile注释和图表中已经详细描述了步骤和效果。3.5CMD和ENTRYPOINT的区别这两个命令都指定了启动容器时执行的命令和对应的参数,但是两者略有不同,如下:CMD:只有最后一个命令才会生效,命令会被参数代替在dockerrunDrop之后;ENTRYPOINT:可以添加命令,比如添加参数;上面构建的newimage镜像使用了ENTRYPOINT,所以我们先测试一下ENTRYPOINT,如下:dockerrun在启动容器时指定参数--urls="http://+:9999",容器正常启动,参数仍然生效,相当于在当前目录下直接执行如下命令:dotnetDockerfileDemo.dll--urls="http://+:9999"现在尝试把ENTRYPOINT换成CMD,如下:#上面Dockerfile中#替换ENTRYPOINT["dotnet","DockerfileDemo.dll"]withCMD,如下:CMD["dotnet","DockerfileDemo.dll"]然后重建一个镜像试试如下:如上图,对于CMD,如果后面在运行容器的时候指定了一个参数,这个参数会代替CMD命令,无法拼接,导致命令不正确,所以报错;但是可以这样执行,如下:如果在当前build的context目录下不想让某些文件参与构建,可以在.dockerignore文件中配置。这个和git中的.gitignore是一样的,写的也比较简单,这里就不演示了。对了,.NetCore的镜像列表可以参考这个地址:https://hub.docker.com/_/microsoft-dotnet-aspnet/,每个镜像都有对应的Dockerfile,感兴趣的朋友可以点进去见见,见参考。综上所述,关于Dockerfile的演示就说这么多了。朋友们一定要举一反三。上面的演示只是一个小例子。在正式的项目中,可以根据自己的需要编辑一个符合要求的Dockefile,最终构建一个方便易用的镜像,这样开发和运维就和谐了(呵呵呵呵)。