专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Docker入门篇(五):DockerFile

我们在上一章曾介绍了如何使用DockerFile来为这个容器绑定一个数据卷,并知道了DockerFile的其中一个功能:Docker利用它来构建一个镜像。

DockerFile遵守着一定的格式。在本章,重点介绍有关DockerFile的具体内容。

DockerFile是什么

DockerFile是用来构建Docker镜像的构建文件,是由一系列命令和参数构成的脚本。

DockerFile使用方式

1、 创建新文件,内容遵守DockerFile的规范。
2、 通过Build命令加载该DockerFile,并生成自定义的镜像。
3、 运行这个镜像。

重新剖析centos镜像

我们从浏览器端进入Docker hub官网。然后通过搜索centos进入到GitHub仓库中。(以centos7.6.1804为例)

FROM scratch
ADD centos-7-docker.tar.xz /

LABEL org.label-schema.schema-version="1.0" \
    org.label-schema.name="CentOS Base Image" \
    org.label-schema.vendor="CentOS" \
    org.label-schema.license="GPLv2" \
    org.label-schema.build-date="20181204"

CMD ["/bin/bash"]

特别留意一下文件中的第一行:其中FROM scratch代表这个镜像源于基础镜像scratch。(不要忘记UnionFS的分层特色)而这个srcatch镜像在DockerFile中的地位,就相当于Object在Java类中的地位:所有衍生的DockerFile都源自于scratch

DockerFile基本格式

1、 每条保留字指令必须全部都是大写,后面至少有一个参数。
2、 指令按照从上到下,顺序执行。
3、 使用#符号表示注释。
4、 每条指令最终会创建出一个新的镜像层,并commit到本地。

一个镜像的构建过程

1、 首先,Docker从FROM指向的父镜像中创建一个容器。
2、 每执行一条指令,就对容器做出一条修改。
3、 执行类commit操作生成一个新的镜像层。
4、 重复步骤2-3,直到所有的语句执行完,并保留最终生成的镜像。

DockerFile,Docker镜像和Docker容器

从应用软件的角度来看,DockerFile,Docker image,Docker container代表软件的三个不同阶段:

1、 DockerFile是软件的原材料。
2、 Docker镜像是软件的交付品。
3、 Docker容器可以是软件的运行状态。

104_1.png

好吧!从一个Java程序员的角度来看,我们还可以这么理解:

1、 DockerFile是由我们编写的*.java文件。
2、 Docker image是javac编译生成的.class文件。
3、 Docker容器是在运行时JVM在内存根据这个.class模板生成的一个个实例。

DockerFile面向开发,Docker镜像成为了交付标准,Docker容器则涉及了运维与开发,三者缺一不可。

DockerFile体系结构

我们在这里给出DockerFile详细的保留字指令:

关键字 作用
FROM 指明父镜像,表示当前镜像源自哪个镜像。
MAINTAINER 镜像维护者的姓名和邮箱地址。格式为:name <mail>
RUN 基于此镜像构建容器时需要运行的基础命令。
EXPOSE 容器对外暴露的端口,如tomcat容器的dockerfile会留下8080端口。
WORKDIR 在创建容器之后,终端默认登录时的初始工作目录。
ENV 设置此DockerFile内的环境变量。
ADD 将宿主机目录下的文件,拷贝到镜像,并自动处理URL以及解压缩tar压缩包
COPY 类似ADD,拷贝文件和目录到镜像当中,但不执行解压缩。存在两种写法:copy s t或者选择copy["s","t"]
VOLUME 容器数据卷,用于数据保存和持久化命令。
CMD 指定一个容器启动时要运行的命令。DockerFile内可以有多个CMD,但是只会执行最后一个,在执行run命令时也可以主动覆盖掉它。
ENTRYPOINT 指定一个容器启动时要运行的命令,与CMD不同,它会追加执行run命令时输入的CMD参数,然后拼接成一条命令。(详细请查阅后文的实战部分)
ONBUILD 当该DockerFile用于构建子镜像时才会执行。

体会CMD与ENTRYPOINT

我们之前已经练习过如何从官网下载tomcat镜像/通过exec修改文件夹/最后正确启动了。但大家而有没有这样一个疑问:为什么我启动tomcat镜像时,似乎完全省略了centos启动等过程,而直接就启动了tomcat服务呢?

原因就是这个tomcat镜像的dockerfile文件已经为我们提供了一站式服务。我们可以追踪到GitHub上的对应dockerfile源码(这里仅展现了一部分):

感兴趣的同学可以点击此链接查看GitHub上的源码。

#我选取的tomcat镜像基于jdk-8.官网目前可以提供到jdk-14的镜像.
FROM openjdk:8-jdk

ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p "$CATALINA_HOME"
WORKDIR $CATALINA_HOME

#....中间完成了大量的额外工作.....

EXPOSE 8080
CMD ["catalina.sh", "run"]

原因就是dockerfile的最后一部分,它使用CMD声明:执行tomcat内部的catalina.sh,并启动服务。

如果我们故意在创建该容器中覆盖该行CMD,比如:

$ docker run -it -p 10000:8080 runnable_tomcat:1.0 ls -l

那么就会发现,它除了打印目录之外,什么都没有做。并且迅速地被Docker关闭了。 接下来咱们来举出一个实际的例子,来对比CMDENTRYPOINT的区别。

实现目标

  • 基于CMD实现,使得centos在启动时打印公网ip地址。
  • 基于ENTRYPOINT,使得centos在启动时打印公网ip地址。

注:本实例和centos本身的操作并没么关系,仅为了叙述CMD和ENTRYPOINT的区别。

由于CMD没有执行/bin/bash,这个centos容器在执行完curl之后就会被马上关闭。

curl命令

curl是一个利用URL语法,在命令行下的文件传输工具。cURL支持的通信协议有FTPFTPSHTTPHTTPSTFTPSFTPGopherSCPTelnet、DICT、FILELDAP、LDAPS、IMAPPOP3SMTPRTSP。 ——百度百科

比如说通过http协议请求百度的主页:

$ curl http://www.baidu.com

另外还有一个查询ip地址的网址:ipinfo.io.。我们就利用这个网址来实现公网IP查询的功能。(速度稍慢,需要等待数秒)

$ curl ipinfo.io

我们再touch一个dockerfile文件,只需要在原先的centos镜像中覆盖上薄薄的一层:

FROM centos
RUN yum install -y curl
CMD curl https://ipinfo.io

然后生成这个镜像。

$ docker build -f ipFile -t ip/centos:latest .

我们当使用这个ip/centos创建容器的时候,在最开始,curl就命令就会替我们查询到公网的IP地址。

现在,一个新问题又出现了:curl是可以带参数的。比如说-i可以显示这个https response的头信息。那我现在想要在创建这个容器的时候执行带参数的curl,此时只能在命令行中覆盖[COMMAND]:

$ docker run ip/centos curl -i https://ipinfo.io

这时,我们使用ENTRYPOINT来对之前的dockerfile文件做些改写,来体验和CMD的区别:

FROM centos
RUN yum install -y curl
ENTRYPOINT curl https://ipinfo.io

此时,我们只需要这样执行(ip/centos/ep是基于修改的dockerfile生成的镜像):

$ docker run ip/centos/ep -i

此时Docker就相当于执行了curl https://ipinfo.io -i。原因就是执行run命令的时候输入的-i拼接到了ENDTRYPOINT当中作为一个完整的CMD去执行。

体会ONBUILD的使用

我们还是拿centos作为父模板。并在这个示例中简单体验ONBUILD的功能。首先touch第一个dockerfile,命名为parent_onbuild。

FROM centos
ONBUILD RUN echo "Parent image has been built."
CMD /bin/bash           

注意:我在ONBUILD中留下了一条RUN语句。而当我们执行build语句构建parent/centos镜像时,构建过程并没有什么改变:

Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM centos
 ---> 470671670cac
Step 2/3 : ONBUILD RUN echo "Parent image has been built."
 ---> Running in 3cb5dc8af44c
Removing intermediate container 3cb5dc8af44c
 ---> 1373d6bec880
Step 3/3 : CMD /bin/bash
 ---> Running in 10b4a59a154b
Removing intermediate container 10b4a59a154b
 ---> 377e1c81ab39
Successfully built 377e1c81ab39
Successfully tagged parent/centos:latest

此刻,再touch一个新的dockerfile,命名为son_centos,注意,这一次我们将基础镜像设置为刚才生成的parent/centos

FROM parent/centos
CMD /bin/bash

我们以这个dockerfile为模板生成son/centos。然后观察创建过程:

Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM parent/centos
# Executing 1 build trigger
 ---> Running in dd7f02353963
Parent image has been built.
Removing intermediate container dd7f02353963
 ---> 0be39ca76dc1
Step 2/2 : CMD /bin/bash
 ---> Running in 6354f02c4f66
Removing intermediate container 6354f02c4f66
 ---> 695ee43d5c5f
Successfully built 695ee43d5c5f
Successfully tagged son/centos:latest

注意,此时命令行会显示# Executing 1 build trigger,显示有一个ONBUILD触发器被触发。然后我们就可以观察到,RUN echo "Parent container has been built."语句被触发了。

简单案例:创建一个预安装vim的centOS镜像

实现目标

我希望通过自定义的centOS镜像有这些功能:

  • 切换初始登录时的默认工作目录(原来的centOS镜像默认是/目录)
  • 安装vim。(我们发现压缩版的centOS并没有安装它)

我们touch出一个文件,然后按照dockerfile的格式编写脚本:

#我们以centos为基础镜像,来制作一个新的镜像。
FROM centos

#标注镜像的作者,可选择项。
MAINTAINER Li Junhu <376781642@qq.com>

#设置本dockerfile内生效的环境变量
ENV MYPATH /usr/local

#指定默认的工作目录
WORKDIR $MYPATH

#通过RUN指定->使用yum命令安装vim
RUN yum -y install vim

#开放端口80.
EXPOSE 80

#通过观察可以发现,只有最后一个CMD生效。如果在run镜像时主动输入CMD,则以下三行CMD会被全部覆盖。
CMD echo $MYPATH
CMD echo "success build ok."
CMD /bin/bash

随后,我们再使用build命令,基于这个dockerfile生成新镜像。

$ docker build -f dockerfile -t ljh/centos:latest .

我们稍微介绍build命令的参数:-f,即file string,输入dockerfile的路径。-t,即target string,指定输出的image文件的路径。.表示将这个image文件保存到docker对应的image文件目录当中。

详细的想象可以通过--help命令查阅。

*宿主机禁用IPv4转发导致的问题

笔者在build过程中报出了这个错误(如果没有报该错误,则可以忽略这段叙述):

[Warning] IPv4 forwarding is disabled. Networking will not work.

原因是宿主机默认禁用了IPv4转发,导致在执行CMD yum -y install vim时无法联网而出错。因此需要在宿主机中更改以下配置文件:

$ vim /usr/lib/sysctl.d/00-system.conf

最末行添加如下设置,并通过$ systemctl restart network重启网络服务。

net.ipv4.ip_forward=1

综合案例:DIY tomcat容器

准备文件

在实现这个案例之前,我们首先需要准备一些文件。创建一个空目录作为我们的工作区:

$ mkdir -p /root/dockerTest/tomcat9

创建一个c.txt。(这个文件没有什么意义,只是等会测试COPY用)

$ touch c.txt

从从官网中下载tomcat9 (core) 和Linux-jdk8的tar.gz包,并拷贝到tomcat9文件夹下:

tomcat | jdk8

现在,tomcat9文件夹下应该有三个文件:

apache-tomcat-9.0.35.tar.gz  c.txt  jdk-8u144-linux-x64.tar.gz

梳理流程

现在,我们来整理思路,如果使用Dockerfile来自己创建一个tomcat镜像,都需要哪些步骤:

1、 首先我们应该清楚,我们自定义的tomcat镜像是基于centos执行的,因此我们选取centos作为我们的基础镜像。
2、 随后我们通过拷贝的形式,将tomcat和jdk的压缩包发送给Docker引擎,并让它解压到镜像内。
3、 当然,在运行tomcat和jdk之前,我们还要做一个额外的工作:配置环境变量
4、 在这一切完成之后,我们最终使用CMD唤醒tomcat的启动脚本startup.sh,然后顺便打印以下tomcat的日志信息。

你也可以选择性的为你的DIY镜像额外装配一些零件,比如说安装vim,或者拷贝一些别的文件到镜像内(这里我们选择c.txt),或者通过VOLUME设定tomcat容器的数据卷。

注意,不同jdk解压包的输出文件夹,名字是不同的。笔者选择的jdk版本是8u144,因此解压的路径是./jdk1.8.0_144/。tomcat同理。因此如果你选择的版本跟笔者不同,下面的dockerfile内部的ENV可能会有细微的差别。

另外,在编写ADD和COPY命令时,注意相对路径的问题。因为笔者将所有的文件全部放入了一个工作区间,因此可以不用考虑路径的问题。

FROM centos
MAINTAINER Li junhu <376781642@qq.com>

#test COPY
COPY c.txt /usr/local/cincontainer.txt

#add tomcat and jdk
ADD jdk-8u144-linux-x64.tar.gz /usr/local
ADD apache-tomcat-9.0.35.tar.gz /usr/local

#install yum
RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH 

#set jdk
ENV JAVA_HOME $MYPATH/jdk1.8.0_144
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

#set tomcat_path
ENV CATALINA_HOME $MYPATH/apache-tomcat-9.0.35
ENV CATALINA_BASE $MYPATH/apache-tomcat-9.0.35
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_BASE/bin

EXPOSE 8080
#startup tomcat
CMD $CATALINA_HOME/bin/startup.sh && tail -F $CATALINA_HOME/bin/logs/catalina.out

随后,我们再使用build命令,基于这个dockerfile生成自定义的tomcat镜像,并且命名为diy_tomcat

$ docker build -f dockerfile -t diy_tomcat:latest .

利用容器卷部署APP

为了在tomcat部署一个现成的web服务,我们进入到码云仓库中,克隆一个样例JavaWeb项目的HTTPS地址。(感谢该作者的无偿分享)

我们在物理机(真·宿主机)下打开IntelliJ IDEA中导入该项目,点击右侧的Maven Project扫描,并使用install将这个项目打成一个war包,发送到centOS虚拟机环境的相应目录。

一般情况下,我们只需要将这个war包拷贝到apache-tomcat-x.x.x/webapps/目录下,然后执行apache-tomcat-x.x.x/bin/目录下的startup.bat(Windows环境)或者startup.sh(Linux环境)来让tomcat来运行我们的web服务。

我们在外部的浏览器可以通过URL来访问对应的服务(这个war包的名字是helloworld。):

http://${host}:${port}/${warName}/

但是,我们希望能够通过容器卷的方式,让tomcat容器运行宿主机本地内的web包。

因此我们需要将宿主机的helloworld.war包所在的目录和虚拟机中tomcat/webapps目录关联起来:

-v /root/dockerTest/tomcat9:/usr/local/apache-tomcat-9.0.35/webapps

我们启动DIY的tomcat容器,并将上述的数据卷添加进去。由于容器和宿主机通过数据卷共享数据,因此只要宿主机在此数据卷/root/dockerTest/tomcat9中放置.jar.war包,虚拟容器中的tomcat就可以将它们运行起来。

$ docker run --name tomcat_helloworld \
-v /root/dockerTest/tomcat9:/usr/local/apache-tomcat-9.0.35/webapps \
-d -p 10000:8080 diy_tomcat

如果一切配置正常,我们就可以在物理机的浏览器访问虚拟机的10000端口,并访问到这样的页面:

(笔者的URL是http://hadoop102:10000/helloworld。hadoop102是笔者为虚拟机配置的主机名。)

104_2.png至此,容器卷 + tomcat的简单应用也结束了。

文章永久链接:https://tech.souyunku.com/41976

未经允许不得转载:搜云库技术团队 » Docker入门篇(五):DockerFile

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们