在上一篇中我们简单介绍了Docker镜像的获取与使用,其中在镜像制作中提到在实际使用中一定要用Dockerfile方式去创建镜像而不要用docker commit方式,那么我们该如何编写Dockerfile呢,在写Dockerfile时又有那些注意点呢?今天我们就来一起学习Dockerfile的编写。
一、什么是Dockerfile?
Dockerfile 是一个用来构建镜像的文本文件,其内容包含了一条条构建镜像所需的指令和说明。
二、从一个简单的例子开始
1、制作一个JDK镜像
我们首先通过制作一个简单的JDK镜像来感受Dockerfile的魅力,既然要制作JDK镜像,我们首先需要准备好需要的安装的JDK安装包,这里我们使用jdk-8u231-linux-x64.tar.gz,接下来就是Dockerfile的编写了:
# 声明所使用的基础镜像FROM ubuntu# 指定构建镜像时的工作目录,后续命令都是基于此目录的,如果不存在则创建WORKDIR /opt/soft/jdk# 将jdk包复制并解压到/opt/soft/jdk目录下ADD jdk-8u231-linux-x64.tar.gz opt/soft/jdk/# 设置环境变量ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarENV PATH=$JAVA_HOME/bin:$PATH
ok,这样我们Dockerfile就编写完毕了,其中涉及到的指令含义后续会专门讲解,大家暂时不需要纠结。我们通过docker build命令制作镜像:
$ sudo docker build -t ubuntu-jdk .Sending build context to Docker daemon 194.2MBStep 1/6 : FROM ubuntulatest: Pulling from library/ubuntu35807b77a593: Pull completeDigest: sha256:9d6a8699fb5c9c39cf08a0871bd6219f0400981c570894cd8cbea30d3424a31fStatus: Downloaded newer image for ubuntu:latest---> fb52e22af1b0Step 2/6 : WORKDIR opt/soft/jdk---> Running in 1526a2a25872Removing intermediate container 1526a2a25872---> e5b5ee6e0f89Step 3/6 : ADD jdk-8u231-linux-x64.tar.gz opt/soft/jdk/---> f22f968c43cdStep 4/6 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231---> Running in a71c57c44b12Removing intermediate container a71c57c44b12---> 876227810405Step 5/6 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar---> Running in 8ff7aefbc820Removing intermediate container 8ff7aefbc820---> f04605d6e9c2Step 6/6 : ENV PATH=$JAVA_HOME/bin:$PATH---> Running in 88f21ce05cc4Removing intermediate container 88f21ce05cc4---> cb70bf70e1e9Successfully built cb70bf70e1e9Successfully tagged ubuntu-jdk:latest
我们先来学习下上面执行的docker build命令,其中 -t 将镜像重新命名为ubuntu-jdk,命令末尾的 “.”指明docker context是当前目录,Docker默认是从docker context中查找Dockerfile,所以一般情况下Dockerfile文件名我们是不需要修改的,如果我们修改了Dockerfile文件名,那一定记得使用 -f 参数去指定要使用的Dockerfile。
接下来我们分析下Docker镜像的创建过程:
1)Step 1:执行FROM,将ubuntu作为基础镜像,这里将tag为latest的ubuntu镜像拉取下来,镜像ID为fb52e22af1b0;
2)Step 2:执行WORKDIR,设置工作目录,这里其实是首先启动ID为fb52e22af1b0的临时容器,然后在其中创建/opt/soft/jdk目录,创建完毕后删除此临时容器,并将此容器保存为ID是e5b5ee6e0f89的镜像。
3)Step 3:执行ADD,将jdk-8u231-linux-x64.tar.gz拷贝并解压到/opt/soft/jdk目录下,并保存为ID是f22f968c43cd的镜像。
4)Step 4 ~ Step 6:执行ENV,先开启临时容器,然后设置环境变量,最后保存为镜像。
5)最终镜像构建成功,镜像ID为cb70bf70e1e9,tag为ubuntu-jdk:latest。
2、查看镜像分层结构
大家看到上面是不是很懵逼,我明明只要制作一个镜像,怎么在执行Dockerfile时还创建了那么多镜像呢?其实在Dockerfile中,它的每条指令都会创建一个镜像层,执行操作后再将此镜像层保存。我们通过docker history可以很清楚看到这一点:
$ sudo docker history ubuntu-jdkIMAGE CREATED CREATED BY SIZE COMMENTcb70bf70e1e9 22 minutes ago bin/sh -c #(nop) ENV PATH=/opt/soft/jdk/jd… 0Bf04605d6e9c2 22 minutes ago bin/sh -c #(nop) ENV CLASSPATH=.:/opt/soft… 0B876227810405 22 minutes ago bin/sh -c #(nop) ENV JAVA_HOME=/opt/soft/j… 0Bf22f968c43cd 22 minutes ago bin/sh -c #(nop) ADD file:610ae1ffb70fff692… 403MBe5b5ee6e0f89 22 minutes ago bin/sh -c #(nop) WORKDIR opt/soft/jdk 0Bfb52e22af1b0 12 days ago bin/sh -c #(nop) CMD ["bash"] 0B<missing> 12 days ago bin/sh -c #(nop) ADD file:d2abf27fe2e8b0b5f… 72.8MB
$ sudo docker history ubuntuIMAGE CREATED CREATED BY SIZE COMMENTfb52e22af1b0 12 days ago bin/sh -c #(nop) CMD ["bash"] 0B<missing> 12 days ago bin/sh -c #(nop) ADD file:d2abf27fe2e8b0b5f… 72.8MB
我们可以看到ubuntu-jdk相比于ubuntu镜像多了很多层,这些层就是我们在执行WORKDIR、ADD、ENV指令时产生的。
细心的同学可以发现了:既然我们在构建jdk镜像时创建了那么多镜像,那为什么通过docker image ls 命令只能看到基础镜像和jdk镜像呢?
$ sudo docker image lsREPOSITORY TAG IMAGE ID CREATED SIZEubuntu-jdk latest cb70bf70e1e9 39 minutes ago 476MBubuntu latest fb52e22af1b0 12 days ago 72.8MBubuntu 18.04 39a8cfeef173 6 weeks ago 63.1MBnginx 1.21.1 08b152afcfae 7 weeks ago 133MB
这里需要大家注意我们构建jdk镜像时产生的那些镜像都是中间镜像,这类镜像会被别的镜像所依赖,如果想看到这些镜像,我们要使用docker image ls -a命令:
$ sudo docker image ls -aREPOSITORY TAG IMAGE ID CREATED SIZE<none> <none> f04605d6e9c2 42 minutes ago 476MBubuntu-jdk latest cb70bf70e1e9 42 minutes ago 476MB<none> <none> 876227810405 42 minutes ago 476MB<none> <none> f22f968c43cd 42 minutes ago 476MB<none> <none> e5b5ee6e0f89 42 minutes ago 72.8MBubuntu latest fb52e22af1b0 12 days ago 72.8MBubuntu 18.04 39a8cfeef173 6 weeks ago 63.1MBnginx 1.21.1 08b152afcfae 7 weeks ago 133MB
3、镜像缓存特性
我们在制作jdk镜像时docker创建了多个中间层镜像,这些镜像一般都是会被重复利用的,无需重新构建,这样便能提升镜像构建效率,为了验证这一点,我们简单修改下我们的Dockerfile,我们在最后加一句java文件生成的操作:
FROM ubuntuWORKDIR /opt/soft/jdkADD jdk-8u231-linux-x64.tar.gz opt/soft/jdk/ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarENV PATH=$JAVA_HOME/bin:$PATHRUN touch test.java
接着我们构建镜像:
$ sudo docker build -t ubuntu-jdk-2 .Sending build context to Docker daemon 194.2MBStep 1/7 : FROM ubuntu---> fb52e22af1b0Step 2/7 : WORKDIR opt/soft/jdk---> Using cache---> e5b5ee6e0f89Step 3/7 : ADD jdk-8u231-linux-x64.tar.gz opt/soft/jdk/---> Using cache---> f22f968c43cdStep 4/7 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231---> Using cache---> 876227810405Step 5/7 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar---> Using cache---> f04605d6e9c2Step 6/7 : ENV PATH=$JAVA_HOME/bin:$PATH---> Using cache---> cb70bf70e1e9Step 7/7 : RUN touch test.java---> Running in e5f462e9c451Removing intermediate container e5f462e9c451---> 7dc9fe245160Successfully built 7dc9fe245160Successfully tagged ubuntu-jdk-2:latest
大家可以很明显看出来,在构建过程中第1~6步都是用了镜像缓存,那我们能不能不使用缓存呢?当然可以我们只需要在构建时加入参数--no-cache即可:
$ sudo docker build --no-cache -t ubuntu-jdk-3 .Sending build context to Docker daemon 194.2MBStep 1/7 : FROM ubuntu---> fb52e22af1b0Step 2/7 : WORKDIR opt/soft/jdk---> Running in 8560b572d2acRemoving intermediate container 8560b572d2ac---> 7cecd9874b7cStep 3/7 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/---> 01d1539300e6Step 4/7 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231---> Running in daa99a7adfe0Removing intermediate container daa99a7adfe0---> 16e8e58ff40bStep 5/7 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar---> Running in e9d598aa1cd3Removing intermediate container e9d598aa1cd3---> 868daae44996Step 6/7 : ENV PATH=$JAVA_HOME/bin:$PATH---> Running in e13199a0ba85Removing intermediate container e13199a0ba85---> 0e9732a41c0eStep 7/7 : RUN touch test.java---> Running in 90d76edd935dRemoving intermediate container 90d76edd935d---> 2c73ecf1af57Successfully built 2c73ecf1af57Successfully tagged ubuntu-jdk-3:latest
三、常用的Dockerfile指令:
Docker为了方便我们制作镜像,提供了多种Dockerfile指令,接下来我们一起看看这些指令都是什么含义:
FORM:指定基础镜像;
MAINTAINER:设置镜像作者;
WORKDIR:设置构建镜像的工作目录,如果目录不存在则自动创建;
COPY:将文件从docker context拷贝到镜像;
ADD:与COPY类似,都是将文件从docker context拷贝到镜像,不同的是当文件是归档类型(tar、tar.gz、zip等)时,会自动解压到目标路径;
ENV:设置环境变量,并可被后面的指令使用;
EXPOSE:指定容器中进程会监听的端口,docker可将该端口暴露出来;
VOLUME:将文件或目录设置为volume;
RUN:在容器中运行指定的指令;
CMD:设置容器启动时运行的指令,当设置多条CMD指令时,只有最后一条生效;
ENTRYPOINT:设置容器启动时运行的命令,当设置多条ENTRYPOINT时,只有最后一条生效。
PS:RUN、CMD和ENTRYPOINT的区别:
1、RUN
执行命令并创建新的镜像层,通常用于镜像构建中软件包安装等操作。
2、CMD
为容器指定默认的启动执行命令,此命令会在容器启动且docker run没有指定其他命令时运行,也就是说CMD中的命令是可以在docker run中被其他命令所覆盖的。
3、ENTRYPOINT
和CMD很像,不同的是ENTRYPOINT指定的命令一定会被执行,即使docker run中指定了其他命令,这也就意味着ENTRYPOINT可以用于让容器以应用程序或服务的形式运行。
好了,以上就是本文的内容,关于Dockerfile的其它指令后续会在各个实践案例中逐步使用。




