镜像的定制实际上就是 定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作 的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令 构建一层,因此每一条指令的内容,就是描述该层应当如何构建。还以之前定制 nginx镜像为例,这次我们使用 Dockerfile 来定制。 在一个空白目录中,建立一个文本文件,并命名为 Dockerfile :(注意Dockerfile文件名必须是“Dockerfile”)构建起器以此来寻找文件。

在开始之前推荐一款好用的软件(小僧前辈推荐使用体验极佳)
先上图:在编辑Dockerfile时极大的帮助了我,用普通的vim在过于笨拙。
image.png
官网地址:https://code.visualstudio.com
插件地址:Remote-SSH 远程编辑服务器项目

为什么要使用Dockerfile

从镜像仓库拉取的镜像与我们实际的生产环境的配置是大不相同的,修改我们对其配置文件单独进行修改,可是在实际工作中,容器的数量是庞大的,依靠手动一个个修改是不现实的。
方式一

比如:我们启动一个nginx容器,首先先得启动容器,然后进入到容器中vim编辑配置文件,这其实一定意义上比nginx直接运行在宿主机上修改配置文件更加繁琐。

方式二

当然还有另一种方法:我们将配置文件的路径做成存储卷,从宿主机上加载文件,这样在宿主机上编辑好,启动时将应用程序加载配置文件的目录与宿主机上的配置文件目录建立关联关系,这样也可加载到宿主机上定制的配置文件。 缺点:如果需要修改配置 文件,则需要将容器重启后才能生效。

方式三 自己制作镜像(有两种方式)
基于容器做
用一个基础镜像,先将其启动起来,交互式进入容器修改配置文件,改的结果保存在最上面的可写层。我们将可写层保存为一个新镜像。
缺点:实际应用环境中,倘若公司中有开发、测试、正式三个环境,当然三个环境的镜像并不相同,因为服务器配置就不相同,因此最少做三个镜像,倘若需要更换应用程序版本的话,就需要将三个环境的镜像重新构建,这只是一个应用程序,倘若环境中容器数量庞大,这样的方式并不高效。
配置模板

启动任何一个应用程序的所服务的主机名,文件家目录,监听端口等等在不同环境中各不一样,但是其配置文件的格式是固定的。 我们将配置文件模板放到一个特定的目录下,将配置信息例如(主机名,监听端口号,家目录路径等等)以一种可接手变量的方式去写入配置文件。

我们将一个镜像启动为一个容器时,在容器内部程序主进程启动之前,先启动一个其他程序,这个程序启动的时候根据特定目录下的配置文件模板,以及镜像启动为容器时向容器传递的环境变量(例:IP,端口….),这个程序将镜像传递进来的每一个变量的值写到模板中对应变量中去并保存,最后由这个程序启动容器的主进程而后退出,相当于替换进程的方式。

  1. 配置文件模板简单例子如下(不可参考仅实例)
  2. server.conf
  3. {
  4. server_name $NGINX_SERVER_NAME;
  5. listen $NGINX_IP:$NGINX_PORY;
  6. root $DOC_ROOT;
  7. }

云原生Cloud Native

  • 配置文件天生就是配置文件模板格式的直接可接收环境变量直接替换。
  • 启动时可以做到不去读配置文件,而是加载当前系统之上的环境变量,就能获得自己的配置。

    Dockerfile是什么

  • Docker可以通过从Dockerfile中读取指令来自动化构建镜像,Dockerfile是一个文本文档,它包含了用户可以在命令行中调用来组装镜像的所有命令。

  • 使用Docker构建,用户可以创建一个自动构建,连续执行多个命令行指令。

注意:

  1. 注释信息“#”开头
  2. 一行一个指令及参数
  3. 前后顺序注意
  4. 第一个非注释行的指令必须是FROM指令,表明做当前镜像要基于哪个基础镜像
  5. 基于Dockerfile做镜像一定要在特定的工作目录下,再次目录下放入Dockerfile文件,并且Dockerfile文件名首字母一定要大写。
  6. 如果在Dockerfile中说明要打包进入很多文件的话,则这些文件必须做好后放置到这个特定目录下面(可以放置到其子目录,不能是父目录)
  7. Dockerfile文件还包含隐藏文件:.dockering。所有写到dockerering文件中的那个路径,在打包时都不包含进去。
    1. 示例演示:
    2. # syntax=docker/dockerfile:1
    3. FROM ubuntu:18.04
    4. COPY . /app
    5. RUN make /app
    6. CMD python /app/app.py
    docker built 做镜像时可以用的环境变量,因为做镜像就要基于镜像先启容器来实现的。
    环境变量
    Dingtalk_20210602143116.jpg
  • ${name:-tom}如果变量name有值则返回对应值,如果无值则返回tom
  • ${name:+tom}如果变量有值则返回tom,如果无值则返回空 ``` [root@autotest ~]# echo ${name:-tom} tom [root@autotest ~]# name=Dreambeer [root@autotest ~]# echo ${name:-tom} Dreambeer

[root@autotest ~]# unset name [root@autotest ~]# echo ${name:+tom}

[root@autotest ~]# name=Dreambeer [root@autotest ~]# echo ${name:+tom} tom

  1. <a name="6sixx"></a>
  2. # 构建镜像
  3. 具体命令详情参照如下文章:[docker built 用法](https://www.yuque.com/docs/share/9f9015c7-d0b8-4303-90ec-bfbfcb1d3e1c?# 《Docker命令(toolkit)》)

docker build [选项] <上下文路径/URL/->

在 Dockerfile 文件所在目录执行:

docker build -t nginx:v3 .

Sending build context to Docker daemon 2.048 kB Step 1 : FROM nginx —-> e43d811ce2f4 Step 2 : RUN echo ‘

Hello, Docker!

‘ > /usr/share/nginx/h tml/index.html —-> Running in 9cdc27646c7b —-> 44aa4490ce2c Removing intermediate container 9cdc27646c7b Successfully built 44aa4490ce2c

从命令的输出结果中,我们可以清晰的看到镜像的构建过程。在 Step 2 中,如 同我们之前所说的那样, RUN 指令启动了一个容器 9cdc27646c7b ,执行了所 要求的命令,并最后提交了这一层 44aa4490ce2c ,随后删除了所用到的这个容 器 9cdc27646c7b 。<br />在这里我们指定了最终镜像的名称`nginx:v3`
<a name="82yJa"></a>
# 镜像构建上下文(Context)
docker build 命令最后有一个“ `.`” `.`表示当前目录,,而 Dockerfile 就应该在当前目录。

docker build 的工作原理:Docker 在运行时分为 Docker 引擎 (也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API, 被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在 本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端 (Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。

当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要 将一些本地文件复制进镜像,比如通过 COPY 指令、 ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎 中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?<br />这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径, docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。<br />比如:`COPY ./package.json /app`<br />这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json` ,也不是复制 Dockerfile 所在目录下的` package.json` ,而是复制上下文 (`context`) 目录下的 `package.json` 。<br />因此, COPY 这类指令中的源文件的路径都是相对路径。<br />这就是为什么 `COPY ../package.json /app` 或者 `COPY /opt/xxxx /app `无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。

如果观察 docker build 输出,我们其实已经看到了这个发送上下文的过程:

$ docker build -t nginx:v3 . Sending build context to Docker daemon 2.048 kB …

比如初学者在发现 `COPY /opt/xxxx /app `不工作后,于是干脆将 Dockerfile 放到了硬盘根目录去构建,结果发现` docker build `执行后,在发送一个几十GB 的 东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让 docker build 打包整个硬盘,这显然是使用错误。<br />一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果 该目录下没有所需文件,那么应该把所需文件复制一份过来。<br />如果目录下有些东西 确实不希望构建时传给 Docker 引擎,那么可以用` .gitignore `一样的语法写一 个` .dockerignore` ,该文件是用于剔除不需要作为上下文传递给 Docker 引擎 的。<br />在后面的文章中提到了[.dockerignore](https://www.yuque.com/docs/share/2d633d41-5438-4102-b1e3-178db2d1b269?# 《Nginx -Dockerfile编写分享》)的具体用法(见文章中目录)<br />如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。 这只是默认行为,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile ,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile 。 当然,一般大家习惯性的会使用默认的文件名 Dockerfile ,以及会将其置于镜像构建上下文目录中。

<a name="jHGat"></a>
# docker build 的其他用法-Git repo 进行构建
docker build 还支持从 URL 构建,比如可以直接从 Git repo 中构建:

$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14 $ docker build https://github.com/twang2218/gitlab-ce-zh.git\#:8.14

Sending build context to Docker daemon 2.048 kB Step 1 : FROM gitlab/gitlab-ce:8.14.0-ce.0 8.14.0-ce.0: Pulling from gitlab/gitlab-ce aed15891ba52: Already exists 773ae8583d14: Already exists

这行命令指定了构建所需的 Git repo,并且指定默认的` master` 分支,构建目录 为 /8.14/ ,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。<br />**用给定的 tar 压缩包构建**

$ docker build http://server/context.tar.gz

如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。 <br />**从标准输入中读取 Dockerfile 进行构建 **

docker build - < Dockerfile

cat Dockerfile | docker build -

如果标准输入传入的是文本文件,则将其视为 Dockerfile ,并开始构建。这种 形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情。<br />**从标准输入中读取上下文压缩包进行构建**

docker build - < context.tar.gz ``` 如果发现标准输入的文件格式是 gzip 、 bzip2 以及 xz 的话,将会使其为上 下文压缩包,直接将其展开,将里面视为上下文,并开始构建。