推荐阅读
- 面试败给微服务?别怕,我带你一起手撕Dubbo,SpringBoot与Cloud
- 微服务架构之春招总结:SpringCloud、Docker、Dubbo与SpringBoot
- “闭关修炼”这么久,吃透这些“微服务”笔记,足够面试涨10K
- 疫情期间“闭关修炼”,吃透这本Java核心知识,跳槽面试不心慌
- 终极手撕之架构大全:分布式+框架+微服务+性能优化,够不够?
Dockerfile是Docker用来构建镜像的文本文件,包括[ # J自定义的指令和格式。可以通过docker build命令从Dockerfile中构建镜像。J : 3 ( i R Z用户可以通过统一的语法命令来根据需求进行配置,通过这份统一的配置文件,在不同的文件上进行分发,需要使用时就可以根据配置文件进行O 3 B G自动化构建,这解决了开发人员构建镜像的复杂过程。
Dockerfile的使用
Dockerfile描述了组装对象的步骤,其中每条指令都% } B t # J o是单独运行的A _ G N ^。除了FROM指令,其他每条命令都会在上一条指令所生V p ` 0 i成镜像的基础上执行,执行完后会生成一个新的镜像层,新的镜像层覆盖在原来的镜像之上从而形成了新的镜像。Dockerfile所生成的最终镜像就是在基础镜像上面叠加一层层的镜像层组建的。
Dockerfile指令
Dockerfile的基本格式如下:
# Comment
INSTRUCTION arguments
在{ 2 B k o g V S ZDockerfile中,指令(INSTRUCTION)不区分大小写,但是为了与参数区分,推荐大写。Docker会顺序执行Dockerfile中的指令,第一: C Y U N ) J T V条指令必须是FROM指令,它用于指定构建镜像的基础镜像。在Dockerfile中以#开头的行是注释,而在其他位置出现的#会被当成参数。
Dockerfile中的指令有FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADDU % y T 4 ; u _、COPY、ENTRYPOING、VOLUME、USER、WORKDIR、ONBUILD,错误的指令会被忽略。下面将详细讲解一些h - ,重要的Dockerk H q ^指令。
FROM
格式: FROM <image> 或者 FROM <image>:<tag>
FROM指令的功能是为后面的指令提供基础镜像,因此Dockerfile必须以Fe ` M q rROM指令作为第一条非注释指令。从公共镜像库中拉取镜像很容易,基础镜Y 6 Q d p k L像可以选择任何有效的镜像。在一个Dockerfile中FROM指令可以出现多次,这样会构建多个镜像。tag的默认值是latest,如果参数ime $ M k %age或者tag指定的镜像不存n 5 ] V N X Z ,在,则返回错; M N误。
ENV
格式: ENV <key> <value> 或者 ENV <key>=<vaD 5 f U / # @ Jlue> ...
ENV指令可以为镜像创建出来的容器声明环境变量。并且在Dockerfile中,ENV指令声明的环境变量会被后面的特定指令(即ENV、ADD、COPY、WORKDIR、EXPOSE、VOLUME、USER)解释使用。
其他指令使用环境变量时,使用格式为$variable_name或者${variabl. I / + x @e_name}。如果在变量面前添加斜杠\\可以转义。如\\$foo或者\\${fooH Z l Z i D ,}将会被转换为$foo和${foo},s % n $ 7 h而不是环境变量9 V k @ g ( L 8 ,所保存的值。另外,ONBUILD指U h C X L c ] ^令不支持环境替换。
COPY
格式: COPY <src> <dest>
COPY指令复制所指向的文件或目录,将它添加到新镜像中,复制的文件或目录在镜像中的路径是<dest>。<src>所指定的源可以有多个j 6 0 E e c w a (,但必须是上下文根目录中的相对路径。不能只用形如 COPY ../something /something这样的指令[ _ S F 9。此外,&l7 @ f B ;t;src>可以使用通配符指[ D g #向所有匹配通配符的文件或目录,例如,COPY home* /mydir/ 表示添加所有以\"hom\"开头的文件到目录/mydir/中。
<f W d j;dest>可以是文件或目录,但必须是目标镜像中的绝对路径或者相对于# ^ n E 2WOf i ` { $RKDIR的相对路径(WORKDIR即Dockerf@ K ) zile中WORKDIR指令指定的? w j v j路径,用来为H a m j A其他指令设置- u + 6 ! !工作F H 3目录)。若<dest>以反斜杠/结尾则其指向的是目录;否则指向文件。<src>同理。若<dest>是一个文件,则<sl i & / ; ]rc>的内容会被写到<dest>中;否则<src>指向的文件或目录中的内容会被复制添加到<dest>目录中。当<src>指定多个源时,<dest>必须是目录。如果<dest>不存在,则路径中不存在的目录会被创建。
ADD
格式:ADD <src> <dest>
ADD与COPY指令在功能上很相似,都支持r C I O 0 j复制本地文件到镜像的功能,但ADD指令还支持其他功能。<src>可以是指向网络文件的URL,4 * | - o d Q此时若<dest>指向一J P 7 u Z O m j Q个目录,则URL! ; w P r必须是完全路径,这样可以获得网络文件的文件名filenameX v Y w # g,该文件会被复制添加到<dest>/<Y : W b X z T , ?; h F dfilename>。比如 ADD http:/i = D F {/example.com/config.property / 会创建文件/config.pr4 $ E poperI D Z dtyF d r v J t 0。
<src>还可以指向一个本地压缩归档文件,该文件会在复制到容器时会被解压提取,如ADD sxample.tar.xz /。但是若URL中的文件为归档文件则不会被解压f L } 1 m U ^提取。
ADD 和 COPY指令虽然功能相似B l 0,但一般推荐使用COPY,因为COPY只4 K J $ s } 1 ]支持本地文件,相比ADD而言,它更加透明7 v : J 2。
EXPOSE
格式: EXPOSB E ZE <port> [<port>/<protocol>...]
EXPOSE指令通知DockV d }er该容器在运行时侦听指定的网络^ k ]端口。可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认n 6 | 5 @ ^ ~ S T值为TCP。这个指令仅仅是声明容器打l b w算使用什么端口而已,并不会自动在宿主机进行端口映射,可以在运行的时候通过docker -p指O 7 2 _ s ) k }定。
EXPOSE 80/tcp
Er x D q G ~ ] ^ 5XPOSE 80/udp
USER
格式: USER <user>[:<group] 或者 USER <UID>[:<GID>]
USE9 5 z i f L dR指令设置了useg r x - k l Or name和user group(可选)。在它之后的RUN,CMD以及ENTZ S ; * 6 q _RYPOINT指令都会以V P / 0 O设 , $ - U I s置的user来执行。
WORKDIR
格式: WORKDIz p U ( m k r 3R /path/to/workdir
WORKDIR指令设置工作目` . B 2录,它之后的RUN、CMD、ENTRYPOINT、COPY以及ADD指令都会在这个工作目录下运行。如果这个工作目录不存在,则会自动创建一个。WORKDIR指令可在Dockerfile中多次使用。如果提供了相对路径,则它将相对于上一个WORK1 : / Q p d *DIR指令的路径。例如
WORKDIR /a
WORKDIR ( J ` u : #;b
WORKDIR c
RUN pwd
输出结果是 /a/b/E } b D dc
RUN
格式1: R6 s / z s r y P sUN <comma* @ b P & 0 . 1 !nd> (shell格式)格式2: RUN [\"executn h % ? Z #able\", \"param1\"d 5 Z, \"paraN & c 3m2\"] (exec格式,推荐使用)
RUN指令会在前一条命令创建出的镜像的基础上创建一个容器,并在容器中运行命令,在命令结束运行后提交容器为新镜像,新镜像被Dockerfile中的下一条指令使用。
RUN指令的两种格式表示命令在容器中的两种运行方式。当使用shell格式时,命令通过/bin/sh -c; l ] b运行。当使用exec格式时,命令是直接运行的,容器不调用shell程序,即容器中没有shell程序。exec格式中的参数会被当成JSON数组被Docker解析,故必须使用双引号而不能使用W y t q } D _ A ^单引号。因为exec格式不会在shell中执行,所以环境变量的参数不会被替换。
比如执行R Z x N Y ) L C NRUN [\"echo\", \"$HOME\"]指令时,$HOf t E P x ] $ME不会做变量替换。如果希望运行shell程序,指令可以写成 RUN [\"/bin/bash\", \"-c\", \"echo\", \"$HOME\"]。
CMD
CMD指令有3种格式。
格式1:CMD <command& W A> (shell格式)格式2:CMD [\"executable\", \"param1\", \I I o ~ } { g"param2\"] (exec格式,推荐使用)格式3:CMD [\"param1{ l X $\", \"param2\"] (为Es x k & 6 xNTRYPOINT指令提供参数)
CMD指令提供容器运行时的默认值k y : 2 + V 3 [,这些默认值可以是一条指令,也可以是一些参数。一个Dockerfile中可以有多条CMD指令B 7 5 Q A,但只有最后一条CMD指令有效。 CMD [\"param1\", \"param2\"]格式是在CM- + _ z k g W |D指令和ENTRYPOINT指令配合时使用的,CMD指令中的参数会添加到ENTRYPOING指令中.使用shell和exa B ! ~ ; ^ v Bec格式时,命令在容器中的运行方式与RUN指令相同。
不同之处在于,RUN指令在构建镜像时执行命令,并生成新的镜像;CMD指令在构建镜像时并不执行任何命令,I ^ s .而是在容器启动时默认将CMD指令作为第一条执行的命令。如果用户在命令行界面运行docker run命令时指定了命令参数,则会覆盖CMD指令中的命令。
ENTRYPOINT
ENTRYPOINT指令有两种格式。
格式1:ENTRYPOINT <command> (shell格式)格式2:ENTRYPOINT [\"executable\", \"param1\", \"param2\"] (exec格式,推荐格式h E , M P)
ENTRYPOINT指令和CMD指令类似,A a , * Z / 9都可以让容器在每次启动时执行相同的命令,但它们之间又有不同。一个Dockerfile中可以有多条ENTRYk O R j b L tPOI7 F l I * h q HNT指令,但只有最后一条ENTRYPOINT指令有效。
当使用Shell格式时,ENTRYPOINT指令会忽略任何CMD指令~ ! 1 U h T和docker run命令的参数,并且会运行在bin/sh -c中。这意味着ENTRYPOINTp @ . &指令进程为bin/sh -c的子进程,进程在容器中的PID将不是1,且不能接受Unix信号。即当使用docker stop <container>y . C ) ~ k *;命令时,命令进程接收不到SIGTERM信号F n ; F A。
推荐使用exec格式,使用此格式时,, 6 W 7 &docker run传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT指令的参数中_ ; ~ G A g * ; %。从ENTRY[ 5 !POINT的使用中可以看出,CMD可以是参数,也可以是e E [ l指令,而ENTRYPOINT只能是命令;另外,docker run命令提供的运行命令参数可以覆盖CMD,但不能覆盖ENTRYPOINT1 H E l B & o。
Dockerfile实践心得
使用标签
给镜像打上标签,有利于帮助了解进镜像~ b 3 9 Q b 0功能
谨慎选择基础S G p Z j镜像
选择基础镜像时,尽量选择当前官方镜像库的肩宽,不同镜像的大小不同X Q 3 3,目前Linux镜像大小由如下关系:
busybox < debian &F A *lt; centos < ubuntu
同时在构建自己的Docker镜像时,只安装和更新必须使用的包。此外相比Ubuntu镜像,更推荐使用Debian镜像,因为C S N ; V m它非常轻量级(目前其大c 2 *小是在100MB以下),并且仍然是一个完整的发布版本。
充分利用缓H H R [ F存
Docker daemon会顺序执行Dockerfile中的指令,而且一旦缓存失效,后续命v M c令将不能使用缓存。为了有效地利用缓f * 4存,需要保证指令的连续性,尽量将所有Dockerfile文件相同的部分都放在前面,而将不同的部分放到后面。
正确使用ADD与COPY命令
当在Dockerfile中的不同部分需要用到不同的文件时,不要一次性地将这些文件都添加到镜像中去,而L 0 + I是在需要时添加,这样也有利于重复利用docker缓存。另外考虑到镜像大小问题,使用ADD指令去获取远程URL中的压缩包不是推荐的做法。应该使用RUN wget或RUN curl代替。这样可以删除解压后不在需要的文件,并且不需要在镜像中在添加一层。
错误做法:I M h B I E x y g
ADD hm r ~ nttp://example.com/big.tar.xz /us% ) i , b ] 8 Or/src/things/
RUN tar -xJf /usr/src/things/big.tar.xK N | f U + _z -C /usr/src/things
RUN make -Ci N } m /usr/src/thz . V )ings all
正确的做法:
RUN mkdir -p /usr/src/things \\
&& cup ` _ c {rl -SL http://` ` gexample.com/big.tar.xz \\
| tar&nbs* | ? O . y | T Sp;-xJC /usr/src/t6 , % 5 8hings \\
 S } F v _;&& make -C /usr/src/things all
RUN指令
在使用较长的RUN指令时可以使用反斜杠\\) U * : ? R _ p 5分隔多行。大部分使用RUN指令的常见是运行apt-wget命令,在该场景下请注意以下几点。
- 不要在8 J d W S ( o 5一行中单独使用指令RUN apt-get update。当软件源更新后,这样做会引起缓z 0 S 6 C m 9 !存问题,导致RUN apt-get install指令运行失败。所以,RUN apt-get update和RP ` Z BUN apt-get install应该写在同一行w g , v # u V w。比如 RUN apt-get update && f [ 6 ~ @ y 3 apt-get iq } v }nstall -y package-1 package-2 package-3
- 避免使用d i & 3 ( f c指令RUN apt-geC U Kt upgrade 和 RUN apt-n ) 2 ? 8 :get dE m l R q ist-upgrade。因为在一个无特权的容器中,一些必要的包会更新失败。如果需要更新一个包(如package-1),) E ,直接使用o Q v C命令RUN apt-get install -y package-1。
CMD和ENT, O J / U @ c o kRYPOINT命令
CMD和ENTRYPOINT命令指定是了容器运行的默认命令,推荐二者结合使用。使用e? o _ ^ B @ F jxec格式的ENTRYPOINT指令设置固定的默认命令和参数,然后使用CMD指令设置可变的参数。
比如下面这个例子:
FROM busybox
WORKDIR /app
COPY&ng % v a I / 6bsp;run.sh /app
RUN chmod&nbso t * @ q g B }p;+x ruf 0 9 F 6n.sh
ENTRYPOINT [\"/app/h w P 8 ; t E Urun.sh\"]
CU u s 0MD [\"param1\N L m L"]
run.sh, = 1 4 u {内容如下:
#!/bin/# I + h vsh
echo \"$@\"
运行后输出结果为param1, DocU ^ Y t I L . +kerfile中CMD和ENTRYPOINT的顺序不重要(CMD写在ENTRYPOINT前后都可以)。
当在windows系统下build dockerfile你可能会遇到这个问题
stand: K _ ^ y 4 D - ~ard_init_linux.go:207: exec user&ns / $ W l u Rbsp;process caused \"no - [ P ? . R J W;such&nM 4 | - B . 5 Y Hbsp;file or directory\"
这是因为sh文件的fileformat是dos,这里需要修改为unix,不需要下载额外的工具,一般我们机器上安装了git会自带git bash,进入git bash,使用vi 编辑,在命令/ ] | p y x行模式下修改(:seL n - _ b S a t ff=unix)。
不要再Dockerfile中做端口映射
使用Dockerfile的EXPOSE指令,虽然可以将容器端口映射在主机端口上,但会破坏Docker的可移植性,且这样的镜像在一q r d B ]台主机上只能启动一个容器。所以端口映射应在docker run命令中用-p 参数指定。
# 不要再Dockerfile中做如下映射
EXPOSE 80:8080
# 仅暴露80端口,需要_ 4 s f另做映射
EXPOSE 80
实践Dockerfile的写法
Java 服务的Dos t gckerFile
FROM openjdk:8-jre-alpine
ENV spring_profiles_a1 K @ ) ~ @ /ctive=dev
ENV&nB - X | z 9 K * [bsp;env_java_debug_enabled=falsD X v ! L d je
EXPOSE&nbf 7 f ~ Z 3 p # Esp;8080
WORKDIR /app
ADD&8 a * * T { 3nbsp;target/s+ U !mcp-web.jar /aD m A 2pp/target/smcp-web.jar
ADD ! ) K u u z j;run.sh /app
ENTRYPOINT ./run.sh
可以看到基础镜像是openjdk,然后设置了两个环境变量,服务访问端口是9090(意味着springboot应用中指定了server.portV s s 1=808q @ y 5 ( 0 m0),设置了工作目录是/app。通过ENTRYPOINT设定了启动镜像时要启动的命令(./run.sh)。这个脚本中的内容如下:
#!/bin/sh
# Set debug options if_ ! J y ] Z : o required
if [ x\"${env_java_debug_enabled}\" != x ]  W : U 1 Pp;&& [ \"$T m t O C{env_java( X c G_debug_enabled}\" !=&m O m 2 Y Unbsp;\"false\" ];&nbW X ? h Z 4 psp;L ~ G bthen
&n= 9 @ & m ? Ybsp; java_debug_args=\"-agentlib:jdwp=transport=dt_{ K 7socket,server=y,suspend=n,address=5005\"
fi
# ex: env_jvm_flags=\"-Xmx1200m -XX:MaxRAM=15n % W |00m\" for&nbsa i V P u Gp;production
java $java_debug_args $env_jvm_flags -XX: ! P V+UnlockExperime{ 5 S c RntalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar target/smcp-) - n I I 7web.jar
如果我们要指定jvm的一些参数,可以通过在环境^ 1 F + C % : 5 k变量中设置env_jvm_flags来指定。
MavI ; x O H s 3en Dockerfile
ma! E Q g }ven的Dockerfile也写的很好,这里我发上来G u 0 $ 1 i也给大家参考下
FROM open) g D u M $ 4jdk:8-jdk
ARG MAVEN_VERSA q x * WION=3.6.3
ARG USER_HOME_DIR=\"/root\"
ARG&nbsN # 1 / W N ;p;SHA=c35a1803a6e70a126e80b2b3ae33eed96i , I /1f83ed74d18fc8 o S d16909b2d44d7dada3203f1ffe@ K K 8 . = = 5 G726c176 [ 9ef8dcca2dcaa9fca676987befeay , + d Bdc9b9f759967a8cb77181c0
ARG&ns E v 2 6 - } @bsp;BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries
RUN M 2 z ; j q Imkdir -p /usr/share/maven /usrk v y w # d/share/maven/ref \\
&& curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \\
&& echo \"${SHA} /tmp/apache-maven.tar.gz\" P q , X 3 O h| s6 4 { / 6 Hha512sum -c&nbs6 / ~ 1p;- \\
&nb% g / 9sp; && tar -xzf&np D r P z p Gbsp;/tmp/apache-maven.tar.gz -C /usrf e D k v r/share/mT } Daven --K T 5 n U t M q )strip-components=1 \\
&&h c = ; rm -f /tmp/apache-maven.tar.gz \\
&am # @ M Pp;& ln -s /usr/share/maven/bin/mvn&nbs; t 3 5 X pp;/usr/bin/mv} N e E ; 5 ? 7n
ENV MAVEN_HOME /usr/share/maven
ENV MA{ , DVEN_CONFIG \"$USER_HOMEz y 9 * M J z M_DIR/.m2\"
COPY mvn-entrypoint.sh /usr/local/bin/mvn-entrypoint.sh
COPY settings-docker.xml /usr/share/maven/reH v A c Jf/
ENTRYPOINT [\"/usr/local/bin/mvn-entrypoint.sh\"( c K p R]
CMD [\"mvn\"]
可以看$ ! t = ? $到它是基于openjdkx L 3 a u h W这个基础镜M U x | t q像来创E $ b建的,先去下载maven的包,然后进行了安装。 然后又设置了MAVEN_HOME和MAVEN_CONFIG这两个环境变量,最后通过mvn-entrypoing.sh来进行了启动。
前端服务的两阶段构建
我有一个前端服务,目录结构如下:
$ ls fronte! V q H ond/
myaccount/ V ` , - ,resources/ third_party/
myaccount目) [ m录下是放置的js,vue等,resources放置的是css,images等。third_party放的是第三方应用。
这里采用了两阶段构建,即采用上一阶段的构建结果作为下一阶段的构建数据
FROM node:alpine as builder
WORKDIR \'/build\'
COPY myaccount ./myaccount
COPY resources ./resourcesd + z , 0 #
COPY third_party ./C 7 7 3 [ 8thi; I $ [ b p Crd_party
WORKDIR b r = g h;\'/build/myaccount\'
RUN np} U l im instad N ` V d pll
RUN&nb* s x | _ psp;npm rebuild node-sass
RUN npm run build
RUN ls /buile : | 9 M Z td/myaccount/U 7 = P / F K pdist
FROM ngin= X B ~ ~ | 8x
EXPOSE&S H 7 ! & 7 Inbsp;80
COPY --from=builder /build/myaccount/dist /usr/share/nginx/html
需要注意结尾的 -( ~ x w y *-from=builder这里和开头是遥相呼应的。
作者:think123
原文链接:https://juejin.im/post/5e60745d518825492e4965ed