Docker 入门教程(七)Dockerfile

前面我们使用 docker commit 制作了一个自己的镜像;
但是这个主要是用于学习以及一些特殊的用途;
实际应用中一般不会通过这种方式制作镜像;
因为 docker commit 只是把变动的文件制作成了镜像;
比如之前文章中创建 test 镜像的时候;
记录的是创建的 /test 这个文件夹;
但是变动的过程无法记录;
没有办法追踪到我是通过 mkdir /test 这个命令创建的 /test 目录;
这样的话就非常不利于追踪历史和维护;
如果要是涉及到编译安装软件等等过程;
那就更加令人崩溃了;
其实对我们来说更重要的是想记录 mkdir /test 这条命令;
因为只要有这条命令;
我们肯定是可以得到 /test 目录这个结果;

FROM 和 RUN

因此 Docker 推出了 Dockerfile 来解救猿类;
Dockerfile 就是一个名叫 Dockerfile 的普通的文本文件;
用来记录制作镜像的命令;
我们找个空白文件夹建一个 Dockerfile 文件;
我们之前制作镜像的时候分为 3 步;

  1. 先是拉取镜像;
docker pull ubuntu

那在 Dockerfile 文件中则是通过 FROM 关键字;

FROM ubuntu
  1. 接着是执行了 mkdir /test 命令;
    那在 Dockerfile 文件中则是通过 RUN 关键字;
FROM ubuntu

RUN mkdir /test
  1. 执行 docker commit 制作镜像
    这一步不体现在 Dockerfile 中了;
    需要使用 docker build 命令;
docker build -t baijunyao/test:v2 .

-t baijunyao/test:v2 : 定义镜像的名称和版本
. : build 的上下文环境目录
build 完成后 push 镜像就跟之前一样;
这里就略过不讲了;

敲黑板划重点注意我标注的箭头了;
接着我要拿它来讲解上面说的让人不太理解的上下文环境目录;
Docker 跟 MySQL 之类的一样也分为客户端和服务端;
干活的是服务端;
客户端主要起调用的作用;
在执行 docker build 的时候是要把文件都发给服务端引擎来 dockerd 处理;
所以这个 . 就是指定上下文目录的;
意思是把当前目录的所有文件都发送给 dockerd ;
为了验证上面所说;
我们把 /bin/bash 文件复制到当前目录再运行看看;

cp /bin/bash .
docker build -t baijunyao/test:v2 .

可以看到发送的数据明显变多了;
但是其实 build 镜像的时候并不需要在这个 bash 文件;
这种发送全部文件的行为明显是不合理的;
要解决这个问题可以借助 .dockerignore
看名字就明显一股浓浓的山寨 .gitignore 的气息;
这正是 Docker 的高明之处;
什么 pullpushcommit 等等通通使用我们已经熟知的 Git 概念;
极大的减少了我们的学习阻碍;
接着创建 .dockerignore 文件并加入 bash

/bash

再次执行 build 命令;

那 1M 的 bash 文件已经不再发送给 dockerd 了;

另外针对上面的 RUN 顺便提一嘴;
如果要 RUN 多个命令可以用 && 连接;
比如说常见的 apt 安装;

apt update && apt install busybox

CMD

CMD 关键字用来指定容器启动后执行的 默认 命令;
修改下 Dockerfile 定义一个 CMD 输出 "Dockerfile CMD Commmand" ;
Dockerfile

FROM ubuntu

RUN mkdir /test

CMD echo "Dockerfile CMD Commmand"

build 镜像;

docker build -t baijunyao/test:v3 .

启动容器;

docker run baijunyao/test:v3

可以看到在 run 完后会输出这句话;

但是注意上面我说过是默认命令;
既然叫默认值那一般是可以替换的;
如果我们在启动容器的时候执行了其他命令;
那就不会再执行 CMD 默认命令了;

docker run baijunyao/test:v3 /bin/echo "test"

可以看到 CMD 的默认命令没有执行;
不同于 RUN 可以定义多个;
CMD 只可以定义一个;
如果定义多个那么后面的会覆盖前面的 CMD;

ENTRYPOINT

那如果想定义一个不会被覆盖的命令就可以使用 ENTRYPOINT
Dockerfile

FROM ubuntu

RUN mkdir /test

CMD echo "Dockerfile CMD Commmand"

ENTRYPOINT echo "Dockerfile ENTRYPOINT Commmand"

我们来重新 build ;

docker build -t baijunyao/test:v4 .

运行容器;

docker run baijunyao/test:v4

截图会出乎你的意料;
"Dockerfile CMD Commmand" 或者说 "test" 都没有输出;
"test" 会覆盖掉 "Dockerfile CMD Commmand" 是可以想到的;
但是连 "test" 都不输出这就有点说不过去了;
这里其实是模式的不同;
RUN 、 CMD、ENTRYPOINT 三种关键字都有两种模式;

  1. shell 模式 这就是上面我们一直用的模式;
    跟在命令行输入命令一样;
  2. exec 模式
    把上面的 shell 改成 exec 模式的话就如下所示;
['/bin/echo', 'test']

结论来了在 shell 模式下 ENTRYPOINT 会直接覆盖掉 CMD 的命令;
那我们用 exec 模式改写下;
Dockerfile

FROM ubuntu

RUN mkdir /test

CMD ["/bin/echo", "Dockerfile CMD Commmand"]

ENTRYPOINT ["/bin/echo", "Dockerfile ENTRYPOINT Commmand"]

再次 build 运行;

docker build -t baijunyao/test:v5 .
docker run baijunyao/test:v5

结果再次出乎意料;
CMD 的命令并没有正常输出;
原因是在 exec 模式下;
CMD 命令会被作为 ENTERPOINT 的参数;
因此上面的命令等于下面这样;

ENTRYPOINT ["/bin/echo", "Dockerfile ENTRYPOINT Commmand", "/bin/echo test"]

这真是为难人了;
那有木有一种方案可以让我正常的符合直觉的同时使用 CMDENTERPOINT
有;但是需要我先把下个关键字讲了;

COPY

COPY 关键字用来将宿主机的文件 copy 到镜像中;
我们上面的 ENTERPOINT 代码转移到文件中;

enterpoint.sh

#!/bin/bash

echo "Dockerfile ENTRYPOINT Commmand"

exec "$@"

注意这里多加了一行 exec "$@" ;
它 的作用是执行接到的参数;
给与写权限;

chmod +x enterpoint.sh

接着改写下 Dockerfile 文件;
Dockerfile

FROM ubuntu

RUN mkdir /test

COPY enterpoint.sh /root/docker/

CMD echo "Dockerfile CMD Commmand"

ENTRYPOINT ["/root/docker/enterpoint.sh"]

上面这几条命令也很容易理解了;
先把宿主机上的 enterpoint.sh 文件复制到容器的 /root/docker/ 目录下;

再次 build 运行;

docker build -t baijunyao/test:v6 .
docker run baijunyao/test:v6

终于如愿以偿;
这里需要再次强调上下文环境;
我们 build 命令使用的是 .
也就是说我们不能获取当前目录外面的内容;
我们来试着把 /bin/bash 文件复制到镜像中;
Dockerfile

FROM ubuntu

RUN mkdir /test

COPY enterpoint.sh /root/docker/
COPY /bin/bash /root/docker/

CMD echo "Dockerfile CMD Commmand"

ENTRYPOINT ["/root/docker/enterpoint.sh"]

果然是报错的;

ADD

ADD 和 COPY 差不多;
比较常见的场景是可以用来解压缩文件;
除了需要解压文件;
这个关键字就记住一句话就行了;
官方推荐用 COPY ;
并不建议用 ADD;

ENV

ENV 用来定义变量;
用空格或者 = 定义;
如果值中有空格就加引号;
比如:

ENV PATH /root/baijunyao
ENV PATH=/root/baijunyao
ENV NAME "Junyao Bai"
ENV NAME="Junyao Bai"

使用变量的时候加上 $ 即可;

FROM ubuntu

ENV DOCKER_PATH=/root/docker

RUN mkdir $DOCKER_PATH

COPY enterpoint.sh $DOCKER_PATH/

CMD echo "Dockerfile CMD Commmand"

ENTRYPOINT ["/root/docker/enterpoint.sh"]

另外需要注意的是有几个系统的变量不能作为 key;
比如说 PATHHOME
这类变量可以直接在 Dockerfile 中使用;

EXPOSE

EXPOSE 的主要作用就是告诉使用者镜像的守护端口;
比如说 MySQL 的 Dockerfile 就会定义 EXPOSE 为 3306 来告诉用户端口号;

白俊遥博客
请先登录后发表评论
  • latest comments
  • 总共1条评论
白俊遥博客

ouxiaohao:写的太棒了,学到了

2019-11-22 10:40:43 回复