上一篇文章的连接:docker容器技术之存储卷(五)


目录

一、前言

二、Dockerfile

2.1制作镜像有两种:

2.2 什么是Dockerfile?

2.3 Dockerfile的语法格式

dockerfile做镜像时的工作逻辑:

.dockeringore:

2.4 dockerfile的指令

MAINTANIER

LABEL

COPY

docker build

ADD

WORKDIR

VOLUME

EXPOSE

ENV

RUN

CMD

ENTRYPOINT

USER

HEALTHCHECK

SHELL

STOPSIGNAL

ARG

ONBUILD



一、前言

比如我们需要一个nginx的镜像,但是因为是不同的环境我们可能需要不同的配置。又或者说我们从docker Hub上脱下来的镜像,也不符合我们的配置,那么我们怎么改呢?

启动容器并修改配置然后重启:我们之前都是启动容器,或者docker exec 连接进去再vi修改。然后还要重启或者reload,但是这样太麻烦了。

使用存储卷方式:假设我们把它对应文件的路径做成存储卷,从我们宿主机找一个目录并在上面编辑好,然后启动容器时把默认加载配置文件的那个路径与宿主机上的目录建立关联关系然后去启动容器,他也能加载到我们在外面配置的文件。这样启动完成就立即完成了。

但是比如我们启动完了之后发现有些参数还是要修改,改完还要重启才能生效。

那么还有其他的方式吗?

自制镜像的方式:

  • 基于容器:那就是自己做镜像,作镜像的方式常用的有两种:1是基于容器做。用基础镜像,他的配置可能不符合我们的需求,那么我们先把他启动起来,交互式连入镜像做修改,改完以后,那么改的结果就保存在最上面的可写层中了,这时候我们把这个可写层保存为新镜像,这时候我们再去创建容器时,根据我们自己所创建的镜像来启动。缺陷就是写死在镜像中了。加入现在我们有三种环境测试、开发、生产,那么他们的设置也是不同的,所以我们至少做三个,如果以后想更新版本呢?删了重做。所以完犊子了。直接把配置弄到镜像里的方式显然是不妥当的。但是我们为什么又需要自制镜像呢?因为别人做的镜像的应用程序特性和配置未必是我们自己所需要的。有可能我们的镜像是自己研发的。互联网上没有的。所以说这种方式不是为了解决镜像配置的问题而存在的。事实上docker配置文件这个层级上的解决文件方案是什么呢?

比如还是nginx需要配置一个虚拟主机,提供一个虚拟服务server,但是他这个虚拟主机名是什么,监听的端口是什么,家目录或者叫文档根路径在什么地方,在不同的环境中可能各不一样,但是配置文件的格式是一样的,所以我们可不可以这样做,把它的配置文件生成一个简单的比如叫server.conf放在/etc/nginx/conf.d/目录下把它做成类似于模板等等。比如server_name我们就通过变量$NGX_SERVER_NAME来获取等如图。我们的意思就是把变量变成能接受参数的变量并进行替换,让用户拿着这个镜像容器启动的时候,让容器的内部的主进程在启动之前先启动一个别的程序,这个程序根据我们的server.conf文件以及用户把镜像启动为容器时,向容器传递的环境变量,简单来说就是传值,如果不传值应该有默认值。没有默认值的就必须要传参数才行。而后这个程序就把用户传递进来的每一个变量的值替换在server.conf文件中并把它保存在/etc/nginx/conf.d/目录下的server.conf文件中,保存完了,由这个程序再启动主进程,该进程在退出。由一个进程启动另一个进程并替换当前进程我们使用exec命令。

这样我们以后基于一个镜像启动多个容器的时候,让镜像拥有不同配置的做法就是向他传变量,而且对应对应容器要能处理这个变量并把变量替换为主进程的配置信息才行。而默认情况下我们的nginx不支持这个功能。所以我们自己做镜像就是做这个实现,我们要把模板和框架准备好。同时这个处理撑起要处理好,由他来启动nginx。这样就出来更好的适用于不同环境的镜像。这也就是为什么对于容器来讲通过环境变量配置是至关重要的。

这个也被称为云原生的。通常就被设置为这样。他的配置文件天生就是这种格式的。这个程序完全可以做到不用多配置文件,直接去加载当前系统之上的环境变量就能获得自己的配置了。甚至不同读取配置文件。直接支持从环境变量获取配置而配置文件。

Linux重要哲学思想之一:用文本文件保存程序的配置信息,从而使得用一个文本编辑器就可以做到配置所有的应用程序了。但在容器化时代这种方式就被颠覆了。我们不建议去改配置文件,而是通过传环境变量来解决问题。

二、Dockerfile

2.1制作镜像有两种:

  • Dockerfile
  • 基于容器制作

2.2 什么是Dockerfile?

Dockerfile就是我们用来构建docker镜像的源码。源码不是指变成的源码,里面没有什么条件语句。就是基本的文本指令。

所以说Dockerfile就是纯文本文件,里面包含了一些指令而已。这些指令是dockerfile当中制作时规定的制作指令,也就一二十个

2.3 Dockerfile的语法格式

语法格式由两类语句组成:

  • #注释信息,由#开头
  • 指令及其参数,一行一个指令一般是这样的。一般指令是写成纯大写的,其 本身不区分大小写。约定俗称是使用大写

是顺序执行的指令。整个dockerfile第一个非注释行的第一个指令必须是FROM指令,指的是做当前镜像必须要基于哪个基础镜像来实现。

所以我们的制作过程是基于已有的镜像基础之上来做的,如果没有,我们需要手动去拼凑去做。这样就麻烦大了。

dockerfile做镜像时的工作逻辑:

我们做docker镜像时不一定是当前目录。但是必须要在某个特定目录下进行才行,找一个专门的目录,在这个目录中放进来我们的dockerfile,而且dockerfile文件名首字母必须大写,如果我们想打包很多文件,那么我们必须做好放在专门目录下(不能是这个目录的父目录,可以是它的子目录中的内容,但是不能超出这个路径的边界)。

.dockeringore:

dockerfile还支持在底下做个隐藏文件叫.dockeringore。这个.dockeringore也是个文本文件,在这个文件中可以写进来一些路径,一行一个,他的作用就是所有在打包时的文件中,所有写到dockeringore文件中的路径在打包时都不包含进去,包括dockeringore本身。

接下来我们使用docker build命令针对这个目录通过读取dockerfile来做docker镜像了。然后做好镜像打好标签,推到仓库里边,就可以用了。

这种通过dockerfile来制作镜像就不用先基于基础镜像来启动容器了。

注意:docker build这个过程只不过是代替我们完成步骤。实际上docker build自己还会隐藏启一个容器,跟我们人工启动是没区别的。因此在dockerfile中我们可以执行很多shell命令的,但是这个shell命令不是我们宿主机的命令而是底层这个镜像所包含的命令,镜像中如果没这个命令,想在dockefile中运行一些shell程序是做不到的。

所以说所有的制作环境是底层镜像启动为容器时所能提供给我们的环境。

而制作过程中我们还可以使用环境变量:

variable:-word是指定默认值为word的字串所表示的内容

如果变量就值就用自己的值

variable:+word是说如果这个变量有值则显示为word的字串所表示的内容,变量没值那就算了。用来判断是否为空时常用相当于单个的if

2.4 dockerfile的指令

tag可省,表示latest,或者使用FROM busybox@sihqwhidqwioh8y12u1490r1h3i,那个是哈希码 ,推荐使用哈希码


下面我们开始做镜像:

下面我们要基于这个基础镜像做一些有效的改进的定制的操作。下面的指令才是关键:

MAINTANIER

depreacted是该指令已经废弃了,且该指令已经被LABEL替换了

MAINTANIER:表示镜像的维护者,作者

LABEL

LABEL:让用户为一个镜像指定各种各样的元数据。kv格式的。可以指定多个kv,所以MAINTANIER就可以定义了

COPY

用于从Docker宿主机中的文件打包提供到目标镜像中去。相当于从宿主机的当前工作目录当中(刚才说过做镜像是要有个目录的)把某一个文件或某些文件复制到目标镜像的文件系统中。

  • COPY一个文件的案例演示

下面我们在dockerfile工作路径下新建一个index.html文件

这index.html文件一定要跟dockerfile在同一目录或同一目录的子目录中

然后添加COPY在dockerfile文件中

下面我们就能开始制作了。先看一下docker build -h的说明

docker build

可以看到能指定很多选项,这些选项都是可省的。

其中PATH就是dockerfile文件所在的父目录

我们做完的镜像只有id号,没有标签也没有所属层,这里我们在做的时候直接打标签,使用-t

到这里镜像就做好了。我们来查看一下

默认这个镜像是启动bash的,但是我们不用交互,我们就看一下这个文件存不存在就ok了。所以我们直接给一个命令让默认的命令不运行,改为运行我们指定的命令。命令一结束容器就停止,在测试的时候很好用

  • COPY一个目录的案例演示

我们的COPY指令可以用多次的,但是在dockerfile中要惜字如金,因为每一条指令都会生成一个新的镜像层,因此如果能把两个指令合成一条就要写成一条。层越多将来联合挂载的时候效率越差

注意我们复制的是目录下的文件,这个目录本身是不会被复制的。所以我们要在目标镜像地址中要写上yum.repos.d/必须要以/结尾,要不就被识别为文件了。

验证

 

ADD

功能跟COPY很相似,也是从Docker宿主机中的文件打包提供到目标镜像中去。但是ADD支持URL路径,意思是如果我们的宿主机在执行docker build的过程中,能访问到网络上的某服务器,它可以自动基于URL的方式把所指定的文件引用下载到本地并打包进文件中。

第二个当我们使用ADD指令的时候,如果后面跟的要复制的源是一个tar.gz或者是tar.sz之类的tar的文件,那么他允许或者会自动把这个文件展开为目录

以上就ADD具有。COPY不具有的。但是除此之外都一样

注意:本地的tar会被自动展开,但是URL获取的tar文件不会自动展开。

  • ADD一个URL的tar文件案例演示

  • ADD一个tar能自动展开的案例演示

先把tar下载到本地目标路径下

编辑Dockerfile

WORKDIR

是用来指定工作路径的,可以指多次。每一次只影响从他开始向后的指令

我们在后面可以再写一个,换句话说当某些操作做了相对路径的引用,会从后往前找WORKDIR,找到的第一个就是工作目录

下图和上图的效果是一样的

VOLUME

我们曾经讲过绑定挂载卷和docker管理的卷,其中绑定挂载卷有一特点,既能指容器中的路径又能明确指宿主机上的目录,但是在Dockerfile中的只能指定挂载点不能指定宿主机的路径。所以他只能是使用docker管理的卷。

举例:

构建镜像:

启动容器并执行mount命令:

或者启动容器的时候使用sleep命令:

然后在另一个终端:

我们在启动容器没使用-v,所以只要镜像中定义了存储卷,当你创建为容器的时候就会自动拥有相关的存储卷

EXPOSE

例子:

下图说明端口未暴露

下面我们让端口暴露

-P用来暴露端口。我们没指定因为Dockerfile中指定了

总结:EXPOSE是指明 待暴露端口(也叫默认暴露端口),不会真正的暴露,除非在运行容器时加了-P选项

当然我们还可以使用-p暴露其他端口

ENV

案例:

如果一次定义多个环节变量:

查看:

当然我们这么玩没意思。我们看一下哦docker run --help

由上图我们看到在docker run的时候是可以向变量传值的。可以设定变量值

而且docker build --help看到也可以向变量传值

而且我们还可以看到在docker run的时候我们可以输出变量信息的,使用printenv参数

所以在dockerfile中所定义的所有环境变量,是可以在启动容器以后直接在容器中使用变量或者说是引用变量,变量会被注入到容器中


  • 例子:初始化容器为镜像时,向环境变量赋值

那么我们在 运行容器 时候修改变量的值会有影响么?

在初始化镜像为容器时,可直接向环境变量赋值,从而可以接收环境信息。

但是我们里面的内容有没有变化呢?

可以看到没有被替换,是因为这个过程在docker build的时候就完成了。所以我们后来改环境变量的值只是环境变量的值变了,并不会影响docker build的过程

RUN

CMD

RUN跟CMD都是运行命令的,但是这两个命令运行的时间点不一样。

有两个阶段,1:docker build作镜像阶段。2:docker run把镜像启动为容器

  • 例子:我们现在把tar文件的URL添加到dockerfile中,并手动展开

注意:我们的tar命令要在busybox中要支持才行,因为基础镜像站一定要有tar命令,没有tar命令则运行不了,所以运行的所有命令是基于基础镜像所提供的环境运行的命令。因此我们要编译安装nginx要有编译安装环境才能编译的。

可以看到如果反复引用定义为变量则更为合适,所以变成如下图

下面我们基于镜像启动:

注意我们设定了工作目录的话。默认进去就是工作目录所指定的位置

验证:

例子:、

通过上图可以看到就卡在这里了

默认运行程序是httpd,所以-it没用。因为没有交互式接口

通过上图可以看到运行的命令,运行的是httpd,是shell的子进程,但是我们可以额外使用exec命令登陆进去

可以看到PID为1了。因为他默认避免他不是1,而默认使用了exec命令做了替换操作,是为了确保容器能自动接收linux信号,当你在执行docker stop的时候他能停掉,当你执行docker kill的时候能停到,要不然他就停不掉了。但这并不妨碍他启动为/bin/sh的子进程的。

下面我们换一个dockefile的写法

这种json数组格式的写法默认并不会以shell子进程的方式来解析

可以看到没有/bin/sh,直接显示一个命令本身。

可以看到报错了。是因为在这种格式下不会默认运行为shell的子进程,而${WEB_DOC_ROOT}是shell变量。

所以要按照下图进行修改,手动把它运行为shell的子进程

可以看到一运行就退出了。那么问题在哪?不知道。大部分我们的-c是没问题的。感觉这里可能和我们的httpd的路径有点问题,先不管了。都是小事

ENTRYPOINT

  • 举个栗子

  • 例子:让nginx能够灵活接收配置,能够通过环境变量来接收参数来决定他监听的地址、端口等

使用-e传变量

USER

 

HEALTHCHECK

举个栗子

隔30s可以看到进程的检测是成功的

 

SHELL

STOPSIGNAL

 

ARG

跟ENV很像

只是这个参数在docker build过程中使用,能够为build命令在执行的时候使用 --build-arg传过来。


下面我们测试一下:

后定义先调用这样ok不,因为FROM只能是第一行

可以看到报错,说这个变量不存在,下面我们修改一下dockerfile

可以看到这次没报错。但是我们没给变量赋值,所以使用的是默认的值,下面我们给变量指定值

可以查看发现作者信息被改变了。

ONBUILD

当别人在写dockerfile的时候FROM了我们的这个镜像的时候会触发ONBUILD命令。

发现没执行下载,下面我们做个新的镜像

 


上一篇文章的连接:docker容器技术之私有registry(七)

 

 

 

 

 

 

 

 

 

 

 

 

更多推荐

docker容器技术之Dockerfile详解(六)