Docker容器技术(四)——Dockerfile详解
1. 编写一个简单的Dockerfile
创建一个Dockerfile
创建Dockerfile尽量不要在根目录,因为默认在构建的时候会把当前目录所有数据发送到docker引擎,如果在根目录,会把跟目录所有数据发送给docker引擎进行构建。
mkdir docker
cd docker/
echo hello > testfile
vim Dockerfile
FROM busybox
COPY testfile /
COPY参数要求要拷贝的文件必须在当前目录,不能写绝对路径,只能是相对路径。拷贝的目的地可以是目录或文件
构建镜像
docker build -t demo:v1 .
查看镜像的分层结构
docker history demo:v1
通过Dockerfile来构建镜像,可以清除的看到每一层都干了什么,有安全审计的功能。而通过docker commit 构建新镜像的方式则无法知晓具体在镜像内做了什么操作,无法对镜像进行审计,存在安全隐患。
镜像的缓存特性
构建镜像中有相同的镜像层时,会使用缓存来加速构建。
vim Dockerfile
FROM busybox
COPY testfile /
RUN echo helloword > file1
docker build -t demo:v2 .
docker history demo:v1
docker history demo:v2
vim Dockerfile
FROM busybox
COPY testfile /
RUN echo helloword > file1
RUN echo haha > file2
docker build -t demo:v2 .
docker history demo:v1
docker history demo:v2
docker run -it --rm demo:v1
docker run -it --rm demo:v2
docker run -it --rm demo:v3
Dockerfile最佳实践
通过Dockerfile构建镜像,有很好的审计功能,比较安全,推荐使用,不推荐使用docker commit 来直接构建
2. Dockerfile详解
2.1 dockerfile常用指令
FROM
:指定base镜像,如果本地不存在会从远程仓库下载。MAINTAINER
:设置镜像的作者,比如用户邮箱等。COPY
:把文件从build context复制到镜像
支持两种形式:COPY src dest 和 COPY [“src”, “dest”]
src必须指定build context中的文件或目录ADD
:用法与COPY类似,不同的是src可以是归档压缩文件,文件会被自动解压到dest,也可以自动下载URL并拷贝到镜像:
ADD html.tar /var/www
ADD http://ip/html.tar /var/wwwENV
:设置环境变量,变量可以被后续的指令使用:
ENV HOSTNAME server1.example.comEXPOSE
:如果容器中运行应用服务,可以把服务端口暴露出去:
EXPOSE 80VOLUME
:申明数据卷,通常指定的是应用的数据挂载点:
VOLUME ["/var/www/html"]WORKDIR
:为RUN、CMD、ENTRYPOINT、ADD和COPY指令设置镜像中的当前工作目录,如果目录不存在会自动创建。RUN
:在容器中运行命令并创建新的镜像层,常用于安装软件包:
RUN yum install -y vimCMD 与 ENTRYPOINT
这两个指令都是用于设置容器启动后执行的命令,但CMD会被docker run后面的命令行覆盖,而ENTRYPOINT不会被忽略,一定会被执行。
docker run后面的参数可以传递给ENTRYPOINT指令当作参数。
Dockerfile中只能指定一个ENTRYPOINT,如果指定了很多,只有最后一个有效。
2.2 dockerfile使用案例
ADD自动解压文件
ADD:用法与COPY类似,不同的是src可以是归档压缩文件,文件会被自动解压到dest,也可以自动下载URL并拷贝到镜像:
ADD html.tar /var/www
ADD http://ip/html.tar /var/www
step1 放一个nginx的压缩包在/root/docker下
step2 修改Dockerfile:
vim Dockerfile
FROM busybox
ADD nginx-1.16.1.tar.gz /
step3 构建镜像:
docker build -t demo:v4 .
ADD的用法与COPY类似,但不同的是它可以将文件自动解压到dest
step4 测试:
docker run -it -rm demo:v4
ENV定义环境变量
ENV:设置环境变量,变量可以被后续的指令使用:
ENV HOSTNAME sevrer1.example.com
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1 #定义环境变量hostname为server1
ADD nginx-1.16.1.tar.gz /
step2 构建镜像:
docker build -t demo:v5 .
step3 测试:
docker run -it --rm demo:v5
env查看到环境变量信息:
VOLUME声明数据卷,在封装应用容器时常用
VOLUME:申明数据卷,通常指定的是应用的数据挂载点:
VOLUME ["/var/www/html"]
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1
ADD nginx-1.16.1.tar.gz /
VOLUME ["/data"]
step2 构建镜像:
docker build -t demo:v6 .
step3 查看镜像的创建历史:
docker history demo:v6
step4 进入容器,创建文件:
docker run -it demo:v6
cd /data/
touch file1
按ctrl+p+q退出
step5 查看demov6的挂载信息:
docker ps #找出对应的容器ID
docker inspect 5231e1ce76fe
docker引擎在启动容器时发现定义了卷,会自动生成一个卷。而docker引擎在启动容器时,自动在本地为它创建了这个目录,并且挂载在容器内,可以让容器读取到本地的数据目录。
step6 进入目录,查看到刚刚创建的文件file:
cd /var/lib/docker/volumes/ebe2016c773e0ae350be1f6dc9c6463bb71009bcb6a8b8355e58509c8741f2fe/_data
ls
step7 在此目录下修改文件,容器中的文件同样会被修改:
echo hello > file1
echo hello >> file1
echo hello >> file1
echo hello >> file1
echo hello >> file1
echo hello >> file1
cat file1
docker ps
docker attach 5231e1ce76fe
/data # ls
/data # cat file1
/data # rm -f file1
按ctrl+p+q退出
step8 释放数据卷:
docker ps
docker rm -f 52 #删除容器(此处使用容器ID的简写,因为只有一个52开头的ID)
docker volume ls #显示所有数据卷
docker volume prune #释放数据卷(删除没有被容器使用的卷)
WORKDIR设置镜像中的当前工作目录
WORKDIR:为RUN、CMD、ENTRYPOINT、ADD和COPY指令设置镜像中的当前工作目录,如果目录不存在会自动创建。
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1
WORKDIR /nginx
ADD nginx-1.16.1.tar.gz /nginx
VOLUME ["/data"]
step2 构建镜像:
docker build -t demo:v7 .
step3 测试:
docker run -it --rm demo:v7
我们可以发现,进入容器时就默认在/nginx这个目录下,而将nginx的包解压在了这个目录中。
这个目录之前并不存在,是WORKDIR自动创建的
CMD与ENTRYPOINT
这两个指令都是用于设置容器启动后执行的命令,但CMD会被docker run后面的命令行覆盖,而ENTRYPOINT不会被忽略,一定会被执行。
docker run后面的参数可以传递给ENTRYPOINT指令当作参数。
Dockerfile中只能指定一个ENTRYPOINT,如果指定了很多,只有最后一个有效。
CMD:
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1
WORKDIR /nginx
ADD nginx-1.16.1.tar.gz /nginx
VOLUME ["/data"]
CMD echo helloword
step2 构建镜像:
docker build -t demo:v8 .
step3 测试:
docker run -it --rm demo:v8
docker run -it --rm demo:v8 sh #命令被覆盖
ENTRYPOINT:
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1
WORKDIR /nginx
ADD nginx-1.16.1.tar.gz /nginx
VOLUME ["/data"]
ENTRYPOINT echo helloword
step2 构建镜像:
docker build -t demo:v9 .
step3 测试:
docker run -it --rm demo:v9
docker run -it --rm demo:v9 sh #命令没有被覆盖
2.3 shell和exec格式的区别
区别一
shell格式底层会调用/bin/sh -c来执行命令,可以解析变量,而exec格式不会
shell格式
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV name redhat
ENTRYPOINT echo $hostname
step2 构建镜像:
docker build -t demo:v10 .
step3 测试:
docker run -it --rm demo:v10
可以看到变量$hostname
的值被输出了
exec格式
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV name redhat
ENTRYPOINT ["/bin/echo","$hostname"]
step2 构建镜像:
docker build -t demo:v11 .
step3 测试:
docker run -it --rm demo:v11
可以看到变量$hostname
没有被解析,而是直接输出了$hostname
所以exec格式需要改写:
vim Dockerfile
FROM busybox
ENV name redhat
ENTRYPOINT ["/bin/sh","-c","echo $hostname"]
再次测试,变量就被解析了
区别二
exec格式时,ENTRYPOINT可以通过CMD提供额外参数,CMD的额外参数可以在容器启动时动态替换。在shell格式时,ENTRYPOINT会忽略任何CMD或者docker run提供的参数
exec格式
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo","hello"]
CMD ["world"]
step2 构建镜像:
docker build -t demo:v12 .
step3 测试:
docker run -it --rm demo:v12
docker run -it --rm demo:v12 linux #输出的world变为linux
docker run -it --rm demo:v12 redhat #输出的world变为redhat
shell格式
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENTRYPOINT echo hello
CMD echo world
step2 构建镜像:
docker build -t demo:v13 .
step3 测试:
docker run -it --rm demo:v13
docker run -it --rm demo:v13 linux #输出的world变为linux
docker run -it --rm demo:v13 redhat #输出的world变为redhat
可以看到,在shell格式时,ENTRYPOINT会忽略任何CMD提供的参数