DockerFile解析

什么是Dockerfile

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

Dockerfile构建三步骤:

1
2
3
4
5
graph TB
A(编写Dockerfile文件) --> B
B(docker build)--> C
C(docker run)

看下centos精简版centos镜像dockerfile。

  • from scratch中的scratch相当于java中的object类,是源镜像。
  • maintainer 作者+邮箱
  • label 标签描述信息
  • default command 即运行后执行

DockerFile构建过程解析

Dockerfile编写规范

  1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. 表示注释

  4. 每条指令都会创建一个新的镜像层,并对镜像进行提交

Docker执行Dockerfile流程

  1. docker从基础镜像运行一个容器
  2. 执行一条指令并对容器作出修改
  3. 执行类似docker commit的操作提交一个新的镜像层
  4. docker再基于刚提交的镜像运行一个新容器
  5. 执行dockerfile中的下一条指令直到所有指令都执行完成

总结

从应用软件的角度来看,Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段,

  • Dockerfile是软件的原材料
  • Docker镜像是软件的交付品
  • Docker容器则可以认为是软件的运行态。

Dockerfile面向开发,Docker镜像成为交付标准,Docker容器则涉及部署与运维,三者缺一不可,合力充当Docker体系的基石。

Dockerfile,需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等;

Docker镜像,在用Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行 Docker镜像时,会真正开始提供服务;

Docker容器,容器是直接提供服务的。

DockerFile体系结构(保留字指令)

保留字指令 解释
FROM 基础镜像,当前新镜像是基于哪个镜像的
MAINTAINER 镜像维护者的姓名和邮箱地址
RUN 容器构建时需要运行的命令
EXPOSE 当前容器对外暴露出的端口
WORKDIR 指定在创建容器后,终端默认登陆的进来工作目录,一个落脚点
ENV 用来在构建镜像过程中设置环境变量
ADD 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包
COPY 类似ADD,拷贝文件和目录到镜像中。将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置
VOLUME 容器数据卷,用于数据保存和持久化工作
CMD 指定一个容器启动时要运行的命令。Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
cmd指令的格式和run相似也是两种格式
shell格式:CMD <命令>

exec格式:CMD [“可执行文件”, “参数1”, “参数2”]
在指定了ENTRYPOINT指令后,用CMD的具体参数<>
ENTRYPOINT 指定一个容器启动时要运行的命令,ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数
ONBUILD 当构建一个被继承的Dockerfile时运行命令,父镜像在被子继承后父镜像的onbuild被触发

Dockerfile实践案例

案例1:自定义centos镜像

已知原始拉取的centos镜像不支持ifconfigvim,我们这里做一个案例,用Dockerfile构建一个支持这两个命令的centos镜像。

1.编写Dockerfile

Dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 继承自本地centos镜像
FROM centos
# 作者及邮箱
MAINTAINER hj<hjabari@qq.com>
# 环境变量:设置进入镜像后的工作目录
ENV MYPATH /usr/local
# 引用环境变量
WORKDIR $MYPATH
# 安装工具
RUN yum -y install vim
RUN yum -y install net-tools
#暴露端口
EXPOSE 80
# 运行构建后会执行的命令,最终只会执行进入终端
CMD echo $MYPATH
CMD echo "success-------ok"
CMD /bin/bash

2.构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[dw@dk mydocker]$ sudo vim Dockerfile2
[dw@dk mydocker]$ sudo docker build -f /mydocker/Dockerfile2 -t mycentos:1.3 .
Sending build context to Docker daemon 3.072kB
Step 1/10 : FROM centos
---> 300e315adb2f
Step 2/10 : MAINTAINER hj<hjabari@qq.com>
---> Running in f9a3aee1bb70
Removing intermediate container f9a3aee1bb70
---> 0680f2af1459
Step 3/10 : ENV MYPATH /usr/local
---> Running in e62b6cf85a66
Removing intermediate container e62b6cf85a66
---> 75b40fb2bcf4
Step 4/10 : WORKDIR $MYPATH
---> Running in a00db920ca3f
Removing intermediate container a00db920ca3f
---> 21724bcf3e5b
Step 5/10 : RUN yum -y install vim
---> Running in e0e927e885dc
......
[dw@dk mydocker]$ sudo vim Dockerfile2
[dw@dk mydocker]$ sudo docker build -f /mydocker/Dockerfile2 -t mycentos:1.3 . # 注意"."不能省
Sending build context to Docker daemon 3.072kB
Step 1/10 : FROM centos
---> 300e315adb2f
Step 2/10 : MAINTAINER hj<hjabari@qq.com>
---> Running in f9a3aee1bb70
Removing intermediate container f9a3aee1bb70
---> 0680f2af1459
Step 3/10 : ENV MYPATH /usr/local
---> Running in e62b6cf85a66
Removing intermediate container e62b6cf85a66
---> 75b40fb2bcf4
Step 4/10 : WORKDIR $MYPATH
---> Running in a00db920ca3f
Removing intermediate container a00db920ca3f
---> 21724bcf3e5b
Step 5/10 : RUN yum -y install vim
---> Running in e0e927e885dc
1
2
3
4
5
6
REPOSITORY         TAG       IMAGE ID       CREATED              SIZE
mycentos 1.3 1e839242ccce About a minute ago 295MB
hj/centos latest a5b1d45db2cf 2 days ago 209MB
atguigu/mytomcat 1.2 3195c86b5066 5 days ago 669MB
tomcat latest c43a65faae57 4 weeks ago 667MB
centos latest 300e315adb2f 6 months ago 209MB

3.启动

1
2
3
[dw@dk mydocker]$ sudo docker run -it mycentos:1.3
[root@3f0cdc92f1d9 local]# pwd
/usr/local

案例2:CMD/ENTRYPOINT 镜像案例

两者都是指定一个容器启动时要运行的命令,但是ENTRYPOINT可以额外为命令添加参数,CMD却不行。

CMD

  • Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
1
docker run -it -p 8888:8080 tomcat ls -l # 最后只会执行ls -l命令,tomcat不会启动

ENTRYPOINT

docker run 之后的参数会被当做参数传递给 ENTRYPOINT,之后形成新的命令组合

制作CMD版可以查询IP信息的容器

curl命令解释

curl命令可以用来执行下载、发送各种HTTP请求,指定HTTP头部等操作。
如果系统没有curl可以使用yum install curl安装,也可以下载安装。
curl是将下载文件输出到stdout

使用命令:curl http://www.baidu.com
执行后,www.baidu.com的html就会显示在屏幕上了

这是最简单的使用方法。用这个命令获得了http://curl.haxx.se指向的页面,同样,如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地。如果下载的是HTML文档,那么缺省的将只显示文件头部,即HTML文档的header。要全部显示,请加参数 -i

1.编写dockerfile
1
2
3
FROM centos
RUN yum install -y curl
CMD ["curl", "-s", "http://cip.cc"]
2.构建运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[dw@dk mydocker]$ sudo docker build -f /mydocker/Dockerfile3 -t myip .
Sending build context to Docker daemon 4.608kB
Step 1/3 : FROM centos
---> 300e315adb2f
Step 2/3 : RUN yum install -y curl
---> Running in cee2a446a78a

[dw@dk mydocker]$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myip latest aae1a79f575a 34 seconds ago 245MB
mycentos 1.3 1e839242ccce 15 hours ago 295MB
hj/centos latest a5b1d45db2cf 2 days ago 209MB
atguigu/mytomcat 1.2 3195c86b5066 6 days ago 669MB
tomcat latest c43a65faae57 4 weeks ago 667MB
centos latest 300e315adb2f 6 months ago 209MB
[dw@dk mydocker]$ sudo docker run -it myip
IP : 120.236.177.121
地址 : 中国 广东 广州
运营商 : 移动

数据二 : 广东省广州市 | 移动

数据三 : 中国广东广州 | 移动

URL : http://www.cip.cc/120.236.177.121

如果我们想再加入一个命令查看ip头:

我们可以看到可执行文件找不到的报错,executable file not found。之前说过,跟在镜像名后面的是 command,运行时会替换 CMD 的默认值。因此这里的 -i 替换了原来的 CMD,而不是添加在原来的 curl -s http://cip.cc 后面。而 -i 根本不是命令,所以自然找不到。

那么如果我们希望加入 -i 这参数,我们就必须重新完整的输入这个命令:

$ docker run myip curl -s http://cip.cc -i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[dw@dk mydocker]$ curl -s -i cip.cc
HTTP/1.1 200 OK
Server: openresty
Date: Sun, 13 Jun 2021 06:01:22 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
X-cip-c: H

IP : 120.236.177.36
地址 : 中国 广东 广州
运营商 : 移动

数据二 : 广东省广州市 | 移动

数据三 : 中国广东广州 | 移动

URL : http://www.cip.cc/120.236.177.36

此时CMD命令就无法实现了,因为会产生命令覆盖,这个时候就用entrypoint。

dockerfile:

1
2
3
FROM centos
RUN yum install -y curl
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[dw@dk mydocker]$ sudo vim Dockerfile4
[dw@dk mydocker]$ sudo docker build -f /mydocker/Dockerfile4 -t myip2 .
Sending build context to Docker daemon 5.632kB
Step 1/3 : FROM centos
---> 300e315adb2f
Step 2/3 : RUN yum install -y curl
---> Using cache
---> 1df2d50b8c95
Step 3/3 : ENTRYPOINT ["curl", "-s", "http://cip.cc"] <--
---> Running in f236ec0f2882
Removing intermediate container f236ec0f2882
---> 891d700ccc99
Successfully built 891d700ccc99
Successfully tagged myip2:latest
[dw@dk mydocker]$

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[dw@dk mydocker]$ sudo docker run -it myip2
IP : 120.236.177.121
地址 : 中国 广东 广州
运营商 : 移动
数据二 : 广东省广州市 | 移动
数据三 : 中国广东广州 | 移动
URL : http://www.cip.cc/120.236.177.121

[dw@dk mydocker]$ sudo docker run -it myip2 -i
HTTP/1.1 200 OK
Server: openresty
Date: Sun, 13 Jun 2021 06:06:19 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
X-cip-c: H

IP : 120.236.177.121
地址 : 中国 广东 广州
运营商 : 移动
数据二 : 广东省广州市 | 移动
数据三 : 中国广东广州 | 移动
URL : http://www.cip.cc/120.236.177.121

案例3:ONBUILD

当构建一个被继承的Dockerfile时运行命令,父镜像在被子继承后父镜像的onbuild被触发。做一个onbuild的镜像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@dk mydocker]# docker build -f /mydocker/Dockerfile4 -t myip_father .
Sending build context to Docker daemon 5.632kB
Step 1/4 : FROM centos
---> 300e315adb2f
Step 2/4 : RUN yum install -y curl
---> Using cache
---> 1df2d50b8c95
Step 3/4 : ENTRYPOINT ["curl", "-s", "http://cip.cc"]
---> Using cache
---> 891d700ccc99
Step 4/4 : ONBUILD RUN echo "father onbuild---------886" <--
---> Running in 70e0c81e2b5f
Removing intermediate container 70e0c81e2b5f
---> 1cbdf842113a
Successfully built 1cbdf842113a
Successfully tagged myip_father:latest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@dk mydocker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myip_father latest 1cbdf842113a 14 seconds ago 245MB
myip2 latest 891d700ccc99 11 minutes ago 245MB
myip latest aae1a79f575a 15 minutes ago 245MB
mycentos 1.3 1e839242ccce 15 hours ago 295MB
hj/centos latest a5b1d45db2cf 2 days ago 209MB
atguigu/mytomcat 1.2 3195c86b5066 6 days ago 669MB
tomcat latest c43a65faae57 4 weeks ago 667MB
centos latest 300e315adb2f 6 months ago 209MB

[root@dk mydocker]# cp
[root@dk mydocker]# cp Dockerfile4 Dockerfile
Dockerfile Dockerfile2 Dockerfile3 Dockerfile4
[root@dk mydocker]# cp Dockerfile4 Dockerfile5
[root@dk mydocker]# vim Dockerfile5

# Dockerfile5
FROM myip_father
RUN yum install -y curl
ENTRYPOINT ["curl", "-s", "http://cip.cc"]

构建,可以看到父镜像在被子镜像继承后触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@dk mydocker]# docker build -f /mydocker/Dockerfile5 -t myip_son .
Sending build context to Docker daemon 6.656kB
Step 1/3 : FROM myip_father <--
# Executing 1 build trigger <--
---> Running in 4ac886846572
father onbuild---------886
Removing intermediate container 4ac886846572
---> 3046036194b2
Step 2/3 : RUN yum install -y curl
---> Running in da7f72244bf5
Last metadata expiration check: 0:19:14 ago on Sun Jun 13 06:00:13 2021.
Package curl-7.61.1-18.el8.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!
Removing intermediate container da7f72244bf5
---> e169ad98e111
Step 3/3 : ENTRYPOINT ["curl", "-s", "http://cip.cc"]
---> Running in 0274c2228b25
Removing intermediate container 0274c2228b25
---> 7172ab2360ca
Successfully built 7172ab2360ca
Successfully tagged myip_son:latest

案例4:自定义tomcat

自定义一个tomcat

编写dockerfile

将对应的压缩包拉到dockerfile的文件夹目录下,便于构建时使用压缩包构建docker镜像。

1
2
3
4
[root@dk tomcat9]# ls
apache-tomcat-9.0.46.tar.gz c.txt jdk-8u212-linux-x64.tar.gz
[root@dk tomcat9]# pwd
/mydocker/mydockerfile/tomcat9

dockerfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FROM centos
MAINTAINER hj<1961663351@qq.com>
# 把宿主机当前上下文的c.txt拷贝到容器的/usr/local路径下
COPY c.txt /usr/local/cincontainer.txt
# 把Java与tomcat添加到容器中
ADD jdk-8u212-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.46.tar.gz /usr/local/
# 安装vim编辑器
RUN yum -y install vim
# 设置工作访问时候的workdir路径,登陆落脚点
ENV MYPATH /usr/local
WORKDIR $MYPATH
# 配置Java与tomcat的环境变量
ENV JAVA_HOME /usr/local/jdk1.8.0_212
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.46
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.46
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
# 容器运行时监听的端口
EXPOSE 8080
# 启动时运行tomcat
# ENTRYPOINT ["/usr/local/apache-tomcat-9.0.46/bin/startup.sh"]
# CMD ["/usr/local/apache-tomcat-9.0.46/bin/catalina.sh", "run"]
CMD /usr/local/apache-tomcat-9.0.46/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.46/bin/logs/catalina.out

构建

1
2
3
4
5
6
7
8
9
10
11
[root@dk tomcat9]# docker build -t hjtomcat . <--由于是在Dockerfile所在的目录下且dockerfile命名规范,所以不需要加-f
Sending build context to Docker daemon 206.5MB
Step 1/15 : FROM centos
---> 300e315adb2f
Step 2/15 : MAINTAINER hj<1961663351@qq.com>
---> Running in 6285570d7fe9
Removing intermediate container 6285570d7fe9
---> f7301d070188
Step 3/15 : COPY c.txt /usr/local/cincontainer.txt
---> b61f1a056f38
Step 4/15 : ADD jdk-8u212-linux-x64.tar.gz /usr/local/

运行

1
2
3
4
5
6
7
8
9
10
11
docker run -d -p 9080:8080 --name myt9
# -d:后台运行tomcat,前台无输出
# -p:指定端口映射
# --name:命名容器
# -v:添加容器卷,将宿主机目录与容器目录对应
# webapps/下是tomcat发布应用的,通过在宿主机对应目录上传应用,方便发布app
-v /mydocker/mydockerfile/tomcat9/test:/usr/local/apache-tomcat-9.0.46/webapps/test
-v /mydocker/mydockerfile/tomcat9/tomcat9logs:/usr/local/apache-tomcat-9.0.46//logs
# 避免出现数据卷目录写权限不够
--privileged=true
hjtomcat9

上面创建了宿主机和docker容器互通的两个目录,其中:

/mydocker/mydockerfile/tomcat9/test/usr/local/apache-tomcat-9.0.46/webapps/test目录对应,通过对宿主机的test目录修改就能同步到docker容器上,下面尝试在本机test目录编写一个小的web应用,看是否tomcat容器同步且能浏览到。

验证

1
2
3
4
5
6
7
8
[root@dk tomcat9]# sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@dk tomcat9]# docker run -d -p 9080:8080 --name myt9 -v /mydocker/mydockerfile/tomcat9/test:/usr/local/apache-tomcat-9.0.46/webapps/test -v /mydocker/mydockerfile/tomcat9/tomcat9logs:/usr/local/apache-tomcat-9.0.46//logs --privileged=true hjtomcat
c38aace076e1ad830c61858650eeb8c36dc59e82d56ec3e71c655b09167d8eaa
[root@dk tomcat9]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c38aace076e1 hjtomcat "/bin/sh -c '/usr/lo…" 6 seconds ago Up 5 seconds 0.0.0.0:9080->8080/tcp, :::9080->8080/tcp myt9
[root@dk tomcat9]#

结合前述容器卷将测试的web服务在test目录发布

以下是一个简单的java web项目所需要的东西:

web.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">

<display-name>test</display-name>

</web-app>

a.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
-----------welcome------------
<%="i am in docker tomcat self "%>
<br>
<br>
<% System.out.println("=============docker tomcat self");%>
</body>
</html>

宿主机往容器卷添加完文件后,查看容器:

重启容器:

访问test目录下的小web应用:

可以看到,访问成功。此时想要修改页面,直接在主机上修改即可,容器卷数据也会有相应的变化。

总结


上一篇 容器数据卷