一、初识
1、Docker 简介:
镜像的定制实际上就是定制每一层所添加的配置、文件。那么如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是Dockerfile。
Dockerfile描述了组装镜像的步骤,其中每一条命令都是单独执行的,除了FROM指令外,其他每一条指令都在上一条指定所生成的镜像基础上执行,执行完会生成一个新的镜像层,新的镜像层覆盖在原来的镜像层之上,从而形成了新的镜像。Dockerfile所生成的最终镜像就是在基础叠加镜像上一层层的镜像层组成的。
在Dockerfile中,指令不区分大小写,但是为了与参数区分,推荐大写。Docker会顺序执行Dockerfile中的指令,第一条必须是FROM指令,它用于指定构建镜像的基础镜像。在Dockerfile中,以#开头的行是注释。
2、FROM指令和RUN指令
FROM指定基础镜像;
格式:FROM <image>或 FROM <image>:<tag>。
FROM指令的功能是为后面的指令提供基础镜像,因此一个有效的Dockerfile必须以FROM指令作为第一条非注解指令。若FROM指令中tag参数为空,则tag默认为latest;若参数image或tag指定镜像不存在,则返回错误。
RUN执行命令;
格式:RUN <command>(shell格式)或RUN [“executable”, “param1“, “param2”](exec格式,非常推荐)。
RUN指令是用来执行命令行命令的。RUN指令会在前一条命令创建出的镜像的基础上创建一个容器,并在容器中运行命令。在命令结束运行后提交新容器为新镜像,新镜像被Dockerfile的下一条指令使用
3、Dockerfile构建一个镜像
步骤:
首先创建一个空文件夹:mkdir newdir;
然后进入该文件夹:cd newdir;
在该文件夹下创建一个名为Dockerfile的文件,根据实际需求补全Dockerfile的内容;
使用Dockerfile构建一个镜像:docker build -t testimage .(注意这个小数点)其中-t指定新镜像的镜像名。
构建第二次参考案例2
例子1:
#先创建一个新的空文件夹
#mkdir newdir
#进入这个新文件夹中
#cd newdir
#创建一个Dockerfile文件
#touch Dockerfile
#补全Dockerfile的内容(为了方便展示,这里用的是echo向Dockerfile中输入内容)
#echo "FROM ubuntu:latest" > Dockerfile
#echo "RUN mkdir /dir1" >> Dockerfile
#使用该Dockerfile构建一个名为testimage的镜像
#docker build -t testimage . //这个点注意有空格
Dockerfile 的内容为下
FROM ubuntu:latest
RUN mkdir /dir1
Docker指令是从上到下一层一层执行的,所以在使用这个Dockerfile构建镜像时,首先执行FROM ubuntu:latest这条指令。
FROM ubuntu:latest指定ubuntu:latest作为基础镜像,也就是将ubuntu:latest镜像的所有镜像层放置在testimage镜像的最下面。
然后执行RUN mkdir dir1指令,前面我们说过,执行RUN指令时,会在之前指令创建出的镜像的基础上创建一个临时容器,在这里的容器Id为c5117d908931,并在容器中运行命令。在命令结束运行后提交新容器为新镜像,并删除临时创建的容器c5117d908931。
在Dockerfile的所有指令执行完后,新镜像就构建完成了!
注意事项,谨慎使用RUN
4、修改前的Dokcerfile文件
修改Dockerfile 构建第二层镜像
FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-component
s=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
在Dockerfile的编写过程中一定要牢记一点:镜像的每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。
例子2
#创建一个空文件夹,并进入其中
mkdir newdir1
cd newdir1
#创建一个Dockerfile文件
touch Dockerfile
#假设我的Dockerfile文件为
#FROM ubuntu
#RUN mkdir dir1
#可以这么写:
# echo 'FROM ubuntu' > Dockerfile
# echo 'RUN mkdir dir1'>> Dockerfile
#输入Dockerfile文件内容
#********** Begin *********#
#以busybox为基础镜像
echo 'FROM busybox'> Dockerfile
#在基础镜像的基础上,创建一个hello.txt文件
echo 'RUN touch hello.txt'>> Dockerfile
#********** End **********#
#使用Dockerfile创建一个新镜像,镜像名为busybox:v1
docker build -t busybox:v1 . //注意这个点 有空格
二、docker build、COPY和ADD
1、docker buid 命令详解
Dockerfile创建完成后,可以使用docker build命令根据Dockerfile构建一个镜像。在上一关中,我们在Dockerfile所在的文件夹下执行docker build -t myimage .这条命令,然后镜像就被构建了
#docker build [OPTIONS] 上下文路径|URL
docker build: 用Dockerfile构建镜像的命令关键词;
[OPTIONS]: 命令选项,常用的指令包括-t指定镜像的名字,-f显示指定Dockerfile,如果不使用-f,则默认将上下文路径下的名为Dockerfile的文件认为是构建镜像的“Dockerfile”;
上下文路径|URL: 指定构建镜像的上下文的路径,构建镜像的过程中,可以且只可以引用上下文中的任何文件
https://docs.docker.com/engine/reference/commandline/build/#tarball-contexts
2、COPY指令和ADD指令
1)COPY复制文件;
格式:COPY <源路径> <目标路径>;
COPY 指令将从构建上下文目录中 <源路径> 的文件或目录复制到新的一层的镜像内的 <目标路径> 位置。<源路径>所指定的源必须在上下文中,即必须是上下文根目录的相对路径!<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR指令来指定,后面介绍)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建目录。
2)ADD更高级的文件复制;
格式:ADD <源路径> <目标路径>;
ADD与COPY指令在功能上十分相似,但是在COPY的基础上增加了一些功能。比如,源路径可以是一个指向一个网络文件的URL,这种情况下,Docker引擎会试图下载这个URL指向的文件到<目标路径>去。
此外,当<源路径>为一个tar压缩文件时,该压缩文件在被复制到容器中时会被解压提取。但是使用COPY指令只会将tar压缩文件拷贝到<目标路径>中
例子:
#创建一个空文件夹,并进入其中
mkdir newdir2
cd newdir2
#创建一个文件夹dir1,将其压缩,然后删除dir1
mkdir dir1 && tar -cvf dir1.tar dir1 && rmdir dir1
#创建一个Dockerfile文件
touch Dockerfile
#假设我的Dockerfile文件为
#FROM ubuntu
#RUN mkdir dir1
#可以这么写:
# echo 'FROM ubuntu' > Dockerfile
# echo 'RUN mkdir dir1'>> Dockerfile
#输入Dockerfile文件内容
#********** Begin *********#
#以busybox为基础镜像
echo 'FROM busybox'> Dockerfile
#并将上下文目录下的dir1.tar“解压提取后”,拷贝到busybox:v3的/
echo 'ADD dir1.tar /'>> Dockerfile
#********** End **********#
#文件内容完毕,在当前文件夹中执行
#********** Begin *********#
#以该Dockerfile构建一个名为busybox:v3的镜像
docker build -t busybox:v3 .
#********** End **********#
三、CMD和ENTRYPOINT指令
1、CMD指令
CMD 指定默认的容器主进程启动命令
格式:CMD <command>(shell格式)或 CMD [“executable”,”param1”,”param2”](exec格式,推荐格式)或 CMD[”param1”,”param2”]。(为ENTRYPOINT指令提供参数)
CMD指令提供容器启动时运行的默认命令,例如ubuntu镜像默认的CMD是/bin/bash,因此我们可以直接使用 docker run -it ubuntu进入bash。
同时也可以使用docker run -it ubuntu cat /etc/os-release,执行该命令后会输出系统版本信息。因为当在执行docker run命令时,如果显示地指定了容器的启动命令,那么会将Dockerfile中CMD设置的默认启动命令覆盖,也就是说:cat /etc/os-release命令会替代成为容器的启动命令,所以输出了系统版本信息。
在指令格式上,一般推荐使用exec格式,因为使用 shell格式时,实际的命令会被包装为 sh -c的参数的形式进行执行。比如:CMD echo $HOME,在实际执行中,会将其变更为:CMD [ "sh", "-c", "echo $HOME" ]。
2、ENTRYPOINT指令
ENTRYPOINT 指定默认的容器主进程启动命令
格式:ENTRYPOINT <command>(shell格式)或ENTRYPOINT [“executable”,”param1”,”param2”]。(exec格式,推荐格式)
ENTRYPOINT和CMD一样,都可以指定容器默认的启动命令,但是它又和CMD有所不同。上面我们说过,用户在执行docker run命令创建并启动容器时,如果指定了启动命令,那么“该启动命令”会覆盖CMD指令设置的默认启动命令,但是ENTRYPOINT设置的启动命令该不能被覆盖。
细心的同学可能发现了CMD命令可以为ENTRYPOINT指令提供参数。实际上,如果使用Dockerfile构建镜像时,既使用了ENTRYPOINT指令,又指定了CMD指令,那么CMD指令的含义就发生了改变, CMD 的内容将作为参数传给 ENTRYPOINT指令,换句话说实际执行时,变成了<ENTRYPOINT> <CMD>。同时,如果执行docker run基于该镜像创建并启动容器,并设置了启动命令时,docker run设置的“启动命令”依然会覆盖CMD的内容,但也仅仅是作为ENTRYPOINT指令的参数。
例子
#创建一个空文件夹,并进入其中
mkdir newdir3
cd newdir3
#创建一个Dockerfile文件
touch Dockerfile
#假设我的Dockerfile文件为
#FROM ubuntu
#RUN mkdir dir1
#可以这么写:
# echo 'FROM ubuntu' > Dockerfile
# echo 'RUN mkdir dir1'>> Dockerfile
#输入Dockerfile文件内容
#********** Begin *********#
#以busybox为基础镜像
echo 'FROM busybox'>Dockerfile
#默认情况下,将启动命令设置为df -Th。要求df命令不能被覆盖,但-Th能够被覆盖。
echo 'ENTRYPOINT ["df"]'>>Dockerfile
echo 'CMD ["-Th"]'>>Dockerfile
#********** End **********#
#文件内容完毕,在当前文件夹中执行
#********** Begin *********#
#以该Dockerfile构建一个名为mydisk:latest的镜像
docker build -t mydisk .
#********** End **********#
四、ENV、EXPOSE、WORKDIR、ARG
1、WORKDIR指令
WORKDIR为其他指令设置工作目录;
格式:WORKDIR <工作目录路径>;
WORKDIR指令为Dockerfile中的任何RUN,CMD,ENTRYPOINT,COPY和ADD指令设置工作目录(或称当前目录)。(也就是说以后各层的当前目录就被改为WORKDIR指定的目录)如果WORKDIR对应的目录不存在,将会自动被创建。
2、ENV指令
ENV设置环境变量;
格式:ENV <key> <value>或ENV <key>=<value>;
这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,还是运行时的应用,都可以直接使用这里定义的环境变量。如下所示,下面是为tomcat设置环境变量并让tomcat自启动的一个Dockerfile片段。ENV设置的环境变量CATALINA_HOME能够被后续定义指令使用。
ENV CATALINA_HOME /var/tmp/apache-tomcat-8.0.45
ENV PATH $PATH: $CATALINA_HOME/bin
ENTRYPOINT $CATALINA_HOME /bin/startup.sh && /bin/bash
3、ARG指令
ARG构建参数;
格式:ARG <参数名>[=<默认值>];
ARG与ENV有些类似,它们都可以被后面的其它指令直接使用,但是它并不是环境变量,这意味着将来容器运行时是不会存在ARG变量的。
Dockerfile中的ARG指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build 中用 --build-arg <参数名>=<值> 来覆盖。
什么时候用ARG,什么时候用ENV?
如果想保存为环境变量,就用ENV;如果只想在Dockerfile中临时使用,就用ARG。
4、EXPOSE指令
EXPOSE暴露端口;
格式:EXPOSE <端口1> [<端口2>...]
EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。如果想要公开容器的端口,必须在docker run是指定-p参数去公开端口或者指定-P参数公开所有被EXPOSE的端口。具体可以参照https://docs.docker.com/engine/reference/run/#expose-incoming-ports 。
例子1
使用ENV配置java与tomcat的环境变量,由于tomcat服务会默认监听8080端口,所以使用EXPOSE暴露端口号。最后使用ENTRYPOINT设置启动命令,使tomcat服务随容器启动而启动
例子1:
FROM ubuntu
WORKDIR /var/tmp
RUN apt-get update && \
apt-get install -y wget && \
wget --no-check-certificate --no-cookies --header "Cookie: o\fraclelicense=accept-securebackup-cookie" http://download.o\fracle.com/otn-pub/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/jre-8u144-linux-x64.tar.gz && \
tar -xzf jre-8u144-linux-x64.tar.gz && \
rm jre-8u144-linux-x64.tar.gz
RUN wget "http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.0.45/bin/apache-tomcat-8.0.45.tar.gz" && \
tar -xzf apache-tomcat-8.0.45.tar.gz && \
rm apache-tomcat-8.0.45.tar.gz
ENV JAVA_HOME /var/tmp/jre1.8.0_144
ENV CATALINA_HOME /var/tmp/apache-tomcat-8.0.45
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin
EXPOSE 8080
ENTRYPOINT /var/tmp/apache-tomcat-8.0.45/bin/startup.sh && /bin/bash
例子2
#创建一个空文件夹,并进入其中
mkdir newdir4
cd newdir4
#创建一个Dockerfile文件
touch Dockerfile
#假设我的Dockerfile文件为
#FROM ubuntu
#RUN mkdir dir1
#可以这么写:
# echo 'FROM ubuntu' > Dockerfile
# echo 'RUN mkdir dir1'>> Dockerfile
#输入Dockerfile文件内容
#********** Begin *********#
#以busybox为基础镜像
echo 'FROM busybox' > Dockerfile
#声明暴露3000端口
echo 'EXPOSE 3000' >> Dockerfile
#将变量var1=test设置为环境变量
echo 'ENV var1=test' >> Dockerfile
#设置工作目录为/tmp
echo 'WORKDIR /tmp' >> Dockerfile
#在工作目录下创建一个1.txt文件
echo 'RUN touch 1.txt' >> Dockerfile
#********** End **********#
#文件内容完毕,在当前文件夹中执行
#********** Begin *********#
#以该Dockerfile构建一个名为testimage:v1的镜像
docker build -t testimage:v1 .
#********** End **********#