前面咱们经过编写Dockerfile文件建立镜像,这是咱们最多见的使用docker部署应用的方式,也间接说明了熟练使用Dockerfile文件的重要程度。那么本章咱们就来重点讲解下Dockerfile经常使用的指令使用和说明以及编写Dockerfile最佳实践。下面咱们先看几个常见的自定义Dockerfile文件:java
一、自定义JDK镜像mysql
FROM ubuntu:16.04 RUN mkdir -p /opt/software COPY jdk1.8.0_161 /opt/software/jdk1.8.0_161 ENV JAVA_HOME /opt/software/jdk1.8.0_161ENV PATH $JAVA_HOME/bin:$PATH
二、前一章的自定义springboot 应用镜像git
FROM ubuntu:16.04 RUN mkdir -p /opt/applications/helloworld &&\ mkdir -p /opt/software COPY jdk1.8.0_161 /opt/software/jdk1.8.0_161 COPY lazy-study-docker-0.0.1-SNAPSHOT.jar /opt/applications/helloworld/ ENV JAVA_HOME /opt/software/jdk1.8.0_161ENV PATH $JAVA_HOME/bin:$PATH CMD java -jar /opt/applications/helloworld/lazy-study-docker-0.0.1-SNAPSHOT.jar
下面咱们已问答的方式讲解Dockerfile经常使用的指令github
问:Dockerfile经常使用有哪些指令?web
答:经常使用指令以下:spring
一、FROMsql
语法:docker
FROM <image>[:<tag>] [AS <name>]
说明:shell
为后续指令设置基础镜像,有效Dockerfile必须以FROM指令开头。仅ARG能够在FROM前面。FROM能够在单个Dockerfile镜像中屡次出现以建立多个镜像,或者使用其中一个构建阶段做为另外一个构建阶段的依赖项。只需在每条新FROM指令以前 。每条FROM指令都清除先前指令建立的任何状态。ubuntu
例子1:
FROM openjdk:latest
例子2:
ARG version=3.7FROM alpine:${version}
#若是在FROM后面想试用ARG定义的变量,则须要这一行
ARG version RUN echo "alpine version is: ${version}"
例子3:
ARG CODE_VERSION=latestFROM base:${version}CMD /code/run-appFROM extras:${version}CMD /code/run-extras
二、RUN
语法:
RUN <command>RUN ["executable", "param1", "param2"]
说明:
RUN指令将在当前镜像之上的新图层中执行任何命令并提交结果。生成一个新的镜像。RUN在下一次构建期间,指令的缓存不会自动失效。相似指令的缓存 RUN apt-get dist-upgrade -y将在下一次构建期间重用。例如,RUN能够经过使用--no-cache 标志使指令的高速缓存无效docker build --no-cache。
更多优化信息能够参考官方文档:
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
例子1:
RUN mkdir -p /usr/local/applications
例子2:
RUN ["mkdir ", "-p", "/usr/local/applications"]
上面只能用双引号,不能用单引号
三、CMD
语法:
CMD ["executable","param1","param2"](这是首选形式)CMD ["param1","param2"](做为ENTRYPOINT的默认参数)CMD command param1 param2(shell形式)
说明:
该CMD指令指定在运行映像时要执行的命令。一个Dockerfile只能有一个CMD命令,若是有多个,只有最后一个CMD命令生效。
更多优化信息能够参考官方文档:
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
例子1:CMD java -jar xxxx.jar例子2:CMD ["/usr/local/jdk8/bin/java", "-jar", "/usr/local/applications/xxxx.jar"]例子3:CMD ["/usr/local/jdk8/bin/java", "--version"]
上面只能用双引号,不能用单引号
四、LABEL
语法:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
说明:
该LABEL指令将元数据添加到镜像。 LABEL是键值对。要在LABEL值中包含空格,请使用引号和反斜杠.
例子1:
LABEL "com.example.vendor"="ACME Incorporated"LABEL com.example.label-with-value="foo"LABEL version="1.0"LABEL description="This text illustrates \that label-values can span multiple lines."
五、MAINTAINER(再也不推荐使用)
语法:
MAINTAINER <name>
说明:
该MAINTAINER指令设置生成的镜像的Author字段。该LABEL指令是一个更灵活的版本,您应该使用它,由于它能够设置您须要的任何元数据,而且能够轻松查看,例如使用docker inspect。要设置与MAINTAINER您可使用的字段对应的标签 :
LABEL maintainer="lazy"
而后能够从docker inspect其余标签中看到这一点。
六、EXPOSE
语法:
EXPOSE <port> [<port>/<protocol>...]
说明:
该EXPOSE指令通知Docker容器在运行时侦听指定的网络端口。您能够指定端口是侦听TCP或者UDP,若是未指定协议,则默认为TCP。
该EXPOSE指令实际上不发布端口。它起到一种文档描述的做用,关于哪些端口要发布。要在运行容器时经过-p publish_port:target_port方式进行实际发布端口。
例子1:EXPOSE 80/tcp例子2:EXPOSE 80/upd
七、ENV
语法:
ENV <key> <value>ENV <key>=<value> ...
说明:
该ENV指令将环境变量<key>设置为该值 <value>。此环境变量将在构建阶段中的全部后续指令使用
例子1:
ENV JAVA_HOME /opt/software/jdk1.8.0_161ENV PATH ${JAVA_HOME}/bin:$PATHCMD echo "java home env is:${JAVA_HOME}"
八、ADD
语法:
ADD [--chown=<user>:<group>] <src>... <dest>ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
说明:
与 COPY 相似,从 build context 复制文件到镜像。不一样的是,若是 src 是归档文件(tar, zip, tgz, xz 等),使用ADD指令的化文件会被自动解压到 dest。
使用ADD须要注意细节问题:
1)src路径必须是构建上下文路径,不能 ADD ../xxxx/xxx /
2)若是src是URL,而且dest不以尾部斜杠结尾,则从URL推断文件名并将文件下载到<dest>/<filename>
3)若是<src>是目录,则复制目录的所有内容,包括文件系统元数据。注意,不复制目录自己,只复制其内容。
4)若是<src>是以识别的压缩格式(identity,gzip,bzip2或xz)的本地 tar存档,则将其解压缩为目录
5)若是<src>直接或因为使用通配符指定了多个资源,则<dest>必须是目录,而且必须以斜杠结尾/
6)若是<dest>不存在,则会在其路径中建立全部缺乏的目录。
例子1:ADD jdk1.8.0_161.tar.gz /opt/software/jdk1.8.0_161例子2:ADD --chown=root:root jdk1.8.0_161.tar.gz /opt/software/
九、COPY
语法:
COPY [--chown=<user>:<group>] <src>... <dest>COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
说明:
与 ADD 相似,从 build context 复制文件到镜像。
例子1:
COPY jdk1.8.0_161 /opt/software/jdk1.8.0_161
例子2:
COPY --chown=root:root jdk1.8.0_161 /opt/software/jdk1.8.0_161
十、ENTRYPOINT
语法:
ENTRYPOINT ["executable", "param1", "param2"] (执行形式,首选)ENTRYPOINT command param1 param2 (shell形式)
说明:
设置容器启动时运行的命令。Dockerfile 中能够有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 以后的参数会被当作参数传递给 ENTRYPOINT。
例子1:ENTRYPOINT ["top","-b", "-H"]例子2:ENTRYPOINT top -b -H
十一、VOLUME
语法:
VOLUME ["/data"]
说明:
VOLUME 指令能够在镜像中建立挂载点,这样只要经过该镜像建立的容器都有了挂载点。经过 VOLUME 指令建立的挂载点,没法指定宿主机上对应的目录。对应到宿主机的目录是由docker管理的,默认路径为/var/lib/docker/随机id/_data.咱们能够经过命令:
docker inspect myhelloworld | grep Mounts -A 10
查看镜像挂载在宿主机的路径列表。
例子1:
VOLUME["/logs"]
十二、USER
语法:
USER <user>[:<group>] orUSER <UID>[:<GID>]
说明:
指定后面RUN CMD ENTRYPOINT指令运行的用户权限,当用户没有组时,默认为root组
例子1:
USER mysql
1三、WORKDIR
语法:
WORKDIR /path/to/workdir
说明:
指定后面RUN CMD ENTRYPOINT指令将在那个目录下进行,一旦指定,WORKIDR后面的目录必须会建立
例子1:
WORKDIR /helloWORKDIR worldRUN pwd
最终pwd命令的输出将是 /hello/world
1四、ARG
语法:
ARG <name>[=<default value>]
说明:
该ARG指令定义了一个变量,用户能够docker build使用该--build-arg <varname>=<value> 标志在构建时将该变量传递给构建器
例子1:
FROM openjdkARG var1ARG var2=defaultval
若是 ARG指令具备默认值,而且在构建时没有传递值,则构建器将使用默认值。
以上为经常使用的Dockerfile指令,想了解更多详细指令说明参考官方文档:
https://docs.docker.com/engine/reference/builder/
问:编写Dockerfile文件用来作什么?
答:咱们的应用程序若是须要以docker容器方式来部署的话,通常须要本身编写Dockerfile文件,经过docker build -t xxxxx -f Dockerfile . 命令构建出docker镜像。注意后面有个“.” 这个“.”表明构建上下文为当前路径。例如COPY 或ADD命令通常都是从当前构建上下文获取文件资源复制到构建的镜像中的。
问:编写Dockerfile通常套路是什么?
答:编写Dockerfile以前咱们须要更多的了解镜像的知识,镜像是有层(layer)的概念,通常的镜像都是一层一层堆叠起来的,这些层对容器来讲是只读的。当经过镜像建立容器的时候,会在当前建立的容器最顶层再建立一个简小的可写层,这个可写层能够给实际运行程序写入,建立目录等操做,一旦容器中止且删除,那么这个可写层随之被删除,也就是说这时容器的程序数据不会被保存,会丢失,因此一般咱们会经过volume或挂载等方式将须要保留的数据绑定/映射到宿主机上。
编写Dockerfile通常套路是基于某个层建立本身层的时候,使用FROM语法,就像上面那样FROM openjdk:latest,这里是基于openjdk镜像层的基础上增长咱们的层。
固然,也可使用多阶段构建方式,多阶段构建方式通常是在同一个Dockerfile文件中有多个FROM xxx命令,而后后面的FROM 下面的COPY --from=0这种方式应用前面的FROM,这属于优化层面的东西,这里暂时不过多讨论,有兴趣读者能够查看官方文档:
https://docs.docker.com/develop/develop-images/multistage-build/
问:咱们怎么找到好的基础镜像,好比上面的openjdk
答:不少基础镜像都是由其官方提供,咱们执行docker search xxxx命令查找镜像,这是会返回一个列表,此时咱们关心的是STARTS和OFFICIAl这两列,STARTS是被标星或点赞数量,OFFICIAl为OK通常就是其官方发布的,以下图所示。
另外若是咱们须要在基础镜作些个性化安装的话(好比须要在操做系统上安装某些字体),咱们也能够自定义基础镜像,好比自定义一个myjdk8镜像Dockerfile文件内容以下:
FROM ubuntu:16.04 MAINTAINER lazy RUN mkdir -p /opt/softwareCOPY jdk1.8.0_161 /opt/software/jdk1.8.0_161 ENV JAVA_HOME /opt/software/jdk1.8.0_161ENV PATH ${JAVA_HOME}/bin:$PATH
上面咱们自定义了目录/opt/software,而后把jdk放到该目录下,而后配置了jdk环境变量。若是须要作其余自定义操做的话,也是相似的套路。
问:编写Dokcerfile须要注意哪些东西
答:编写Dockerfile就像写代码,须要考虑优化问题。说到优化,咱们知道,都是有目标和指标的,好比web应用的优化一般是优化速度,指标通常有响应时间、吞吐量、并发数等。想要知道如何编写好的Dockerfile文件就要知道Dockerfile的优化目标和指标。
Dockerfile优化目标为:减小构建出最终的镜像的大小
Dockerfile优化指标为:减小构建出最终的镜像的大小
1)最小化层数
Dockerfile文件每一条指令如RUN,COPY等都会建立一个新的层,因此在写的时候尽可能减小命令,能够利用shell &&的方式减小命令次数好比下面:
RUN mkdir -p /dir1RUN mkdir -p /dir2RUN mkdir -p /dir3优化后为:RUN mkdir -p /dir1 && \ mkdir -p /dir1 && \mkdir -p /dir1
这样就会由三层变成一层,能够减小构建时间和最终的镜像大小
2)不要安装没必要要的包
3)必要时也可使用STDIN中的DOCKERFILE从本地构建上下文构建
语法以下:
mkdir examplecd exampletouch somefile.txt docker build -t myimage:latest -f- . <<EOFFROM busyboxCOPY somefile.txt .RUN cat /somefile.txtEOF
4)若是想从某个不包含Dockerfile文件的的项目git仓库构建镜像,可使用STDIN中的DOCKERFILE从远程构建上下文,就像下面这样:
docker build -t myimage:latest \ -f- https://github.com/xxx/xxx.git <<EOFFROM busyboxCOPY README.md . EOF
5)推荐使用多阶段构建
多阶段构建容许您大幅减少最终图像的大小,官方文档:
https://docs.docker.com/develop/develop-images/multistage-build/
6)利用构建缓存
在检查每条指令时,Docker会在其缓存中查找能够重用的现有映像,而不是建立新的(重复)映像。
若是您根本不想使用缓存,则可使用docker build命令中的--no-cache=true 选项
问:如何忽略构建上下文的某些文件或目录?
答:能够在构建上下文的目录中建立.dockerignore文件,里面记录须要排除的文件或目录内容
更多优化建议能够自行参考官方文档:
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/