Docker 实战操作之使用Dockerfile构建镜像(五)

一、前言

场景需求:

例如运行一个nginx容器,容器起来后,通常不会运行在默认配置下,那因此,我们通常需要去改一改它的配置文件或者定义模块化配置文件,然后启动服务。那为什么,nginx的默认配置不符合我们的需要呢?很显然,不同的生产场景所需要用到的配置参数各个相同,因此,对方只能用一个默认的,认为适用于大多数普遍场景情形的或者适用于较小主机资源情况下的那么一个设定来启动服务,同样的逻辑,各位试想一下,如果我们从docker hub下拖下来一个nginx的镜像,去启动nginx容器的时候,请问这个镜像内的nginx容器内的配置文件一定就会符合我们的需要吗?应该说叫一定不会,基本上几乎不能完全适合我们的需要,此时我们就必须去要修改配置,那怎么改呢?

方式1: 启动容器docker exec连进容器,在内部执行vi,再reload重启

方式2: 假设我们把它对应的那个配置文件的路径做存储卷,从我们宿主机上加载文件,在宿主机上进行编辑,也能让它立即生效,启动容器之前先把它编辑好(容器启动之前,我们事先找一目录把配置文件准备好,然后启动容器时,把容器内的应用程序默认加载配置文件的路径与宿主机上的目录进行建立关联关系,然后去启动容器,也能加载到在宿主机上定制的配置文件)

缺点 :我们在宿主上做的编辑,能不能让它立即生效呢?比如我们启动以后发现,有些参数还是需要改,改完以后依然需要重载才能生效。

方式3: 基于容器自制镜像

先启动起来,交互式连入进来,做修改,改完以后,改的结果一定时保存在最上层的可写层的。这个时候我们把可写层保存在一个新镜像中,而后,我们再去创建容器时,根据我们自己所创建的镜像来使用。

缺点: 做的镜像也是直接把文件直接备进镜像中的,直接写死在镜像中的就是,如果我们想在改,还是改不了,运行过程当中去修改配置的需求可能对于做运维来讲,变更不就是日常操作吗?很多时候,也有可能需要随时进行修改。那依然解决不了问题。而且这种备进镜像的设计方式,最悲惨的地方在于:一次更新,维护复杂 。环境简单可以使用。

方式4 :基于Dcokerfile制作镜像,也就是本文的主角

dockerfile,相当于是一个文档,客户可以基于dockerfile生成新的容器

dockerfile仅仅是用来制作镜像的源码文件,是构建容器过程中的指令,docker能够读取dockerfile的指定进行自动构建容器,基于dockerfile制作镜像,每一个指令都会创建一个镜像层,即镜像都是多层叠加而成,因此,层越多,效率越低,创建镜像,层越少越好。因此能在一个指令完成的动作尽量通过一个指令定义。

Dockerfile可以使用环境变量,用ENV来定义环境变量,变量名支持bash的变量替换,

如${variable:-word},表示如果变量值存在,就使用原来的变量,变量为空时,就使用word的值作为变量的值,一般使用这个表示法。

${variable:+word},表示如果变量存在了,不是空值,那么变量将会被赋予为word对应的值,如果变量为空,那么依旧是空值

基于Dockerfile生成镜像原理图如下:


二、环境介绍

  1. Dockerfile中所用的所有文件一定要和Dockerfile文件在同一级父目录下,可以为Dockerfile父目录的子目录
  2. Dockerfile中相对路径默认都是Dockerfile所在的目录
  3. Dockerfile中一定要惜字如金,能写到一行的指令,一定要写到一行,原因是分层构建,联合挂载这个特性。 Dockerfile中每一条指令被视为一层
  4. Dockerfile中指明大写(约定俗成)

三、编译镜像

3.1 dockerfile编译指令

Dockerfile类似于Makfile,用户使用 docker build 就可以编译镜像,使用该命令可以设置编译镜像时使用的CPU数量、内存大小、文件路径等

语法:docker build [OPTIONS] PATH| URL| - 常见选项: -t 设置镜像的名称和TAG,格式为name:tag -f Dockerfile的名称,默认为PATH/Dockerfile 例子:docker build -f ~/php.Dockerfile . 注意:PATH是编译镜像使用的工作目录,Docker Daemon在编译开始时,会扫描PATH中的所有文件,可以在编译目录中加入.dockerignore过滤不需要的文件

Docker Daemon从Dockerfile中顺序读取指令,生成一个临时容器,在容器中执行指令,容器编译成功后会提交作为镜像层加入最终镜像,为了加快编译过程,Docker Daemon采用了缓存机制,如果在缓存中找到了需要的中间镜像则直接使用该镜像而不生成临时容器(编译时可以使用选项–no-cache选择不使用缓存)

3.2 dockerignore文件

编译开始前,Docker Daemon会读取编译目录中的.dockerignore文件,忽略其中的文件和目录,在其中可以使用通配符(?代表一个字符,*代表零个或任意个字符),使用通配符时,总会出现那么几个例外,这时可以使用!+文件名,Docker Daemon会读取!后面的文件

*/temp* 忽略PATH路径下一级子目录中以temp开头的文件和目录,如PAHT/A/temp.txt
*/*/temp* 忽略PATH路径下二级子目录中以temp开头的文件和目录,如PATH/A/B/temp.txt *.md !README.md 忽略所有md文件,除了README.md

四、Dockerfile指令详解

Dockerfile由多条指令组成,每条指令在编译镜像时执行相应的程序完成某些功能,由指令+参数组成,以逗号分隔,#作为注释起始符,虽说指令不区分大小写,但是一般指令使用大些,参数使用小写


FROM

FROM指令是最重要的一个且必须为 Dockerfile文件开篇的第一个非注释行,用于为映像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境 .

实践中,基准镜像可以是任何可用镜像文件,默认情况下, docker build会在 docker主机上查找指定的镜像文件,在其不存在时,则会从 Docker Hub Registry上拉取所需的镜像文件 .如果找不到指定的镜像文件, docker build会返回一个错误信息

指令:FROM 
功能描述:设置基础镜像 
语法:FROM < image>[:< tag> | @< digest>] 
提示:镜像都是从一个基础镜像(操作系统或其他镜像)生成,可以在一个Dockerfile中添加多条FROM指令,一次生成多个镜像 
注意:如果忽略tag选项,会使用latest镜像

MAINTAINER(已经废弃) ---->LABEL

用于让镜像制作者提供本人的详细信息

Dockerfile并不限制 MAINTAINER指令可在出现的位置,但推荐将

其放置于 FROM指令之后

  • 新版docker中使用LABEL指明
指令:MAINTAINER 
功能描述:设置镜像作者 
MAINTAINER  <authtor's detail> l <author's detail>可是任何文本信息,但约定俗成地使用作者名称及邮件地址,如
MAINTAINER "sunny <sunny@ghbsunny.cn>"
一般把MAINTAINER放在FROM后面

LABEL

功能是为镜像指定标签

指令:LABEL 
功能描述:设置镜像的标签 
延伸:镜像标签可以通过docker inspect查看 
格式:LABEL < key>=< value> < key>=< value> … 
提示:不同标签之间通过空格隔开 
注意:每条指令都会生成一个镜像层,Docker中镜像最多只能有127层,如果超出Docker Daemon就会报错,如LABEL ..=.. <假装这里有个换行> LABEL ..=..合在一起用空格分隔就可以减少镜像层数量,同样,可以使用连接符\将脚本分为多行,镜像会继承基础镜像中的标签,如果存在同名标签则会覆盖
# 一个Dockerfile种可以有多个LABEL,如下:
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
 但是并不建议这样写,最好就写成一行,如太长需要换行的话则使用\符号
LABEL multi.label1="value1"   multi.label2="value2"  other="value3"

ADD

一个复制命令,把文件复制到镜像中。 如果把虚拟机与容器想象成两台linux服务器的话,那么这个命令就类似于scp,只是scp需要加用户名和密码的权限验证,而ADD不用。

  • 路径的填写可以是容器内的绝对路径,也可以是相对于工作目录的相对路径,推荐写成绝对路径
  • 可以是一个本地文件或者是一个本地压缩文件,还可以是一个url
  • 如果把写成一个url,那么ADD就类似于wget命令

注意事项:

  • src为一个目录的时候,会自动把目录下的文件复制过去,目录本身不会复制
  • 如果src为多个文件,dest一定要是一个目录
  • URL不能是ftp格式的url
指令:ADD 
功能描述:复制文件到镜像中 
语法:ADD < src>… < dest>|[“< src>”,… “< dest>”] 
注意:当路径中有空格时,需要使用第二种方式 
当src为文件或目录时,Docker Daemon会从编译目录寻找这些文件或目录,而dest为镜像中的绝对路径或者相对于WORKDIR的路径 
src为目录时,复制目录中所有内容,包括文件系统的元数据,但不包括目录本身 
src为压缩文件,并且压缩方式为gzip,bzip2或xz时,指令会将其解压为目录 
如果src为文件,则复制文件和元数据 
如果dest不存在,指令会自动创建dest和缺失的上级目录
ADD test relativeDir/ 
ADD test /relativeDir
ADD http://example.com/foobar /

如果 <src> 为URL且 <dest> 不以/结尾,则 <src> 指定的文件将被下载并直接被创建为 <dest>

如果 <dest> 以/结尾,则文件名URL指定的文件将被直接下载,并保存为 <dest> / <filename> ,注意,URL不能是ftp格式的url

如果 <src> 是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于“tar -x”命令,但是,通过URL获取到的tar文件不会自动展开

如果 <src> 有多个,或其间接或直接使用了通配符,则 <dest> 必须是一个以/结尾的目录路径;如果 <dest> 不以/结尾,则其被视作一个普通文件, <src> 的内容将被直接写入到 <dest>

COPY

看这个名字就知道,又是一个复制命令

与ADD的区别

  • COPY的只能是本地文件,其他用法一致
指令:COPY 
功能描述:复制文件到镜像中 
语法:COPY < src>… < dest>|[“< src>”,… “< dest>”] 
提示:指令逻辑和ADD十分相似,同样Docker Daemon会从编译目录寻找文件或目录,dest为镜像中的绝对路径或者相对于WORKDIR的路径
<src>:要复制的源文件或目录,支持使用通配符
<dest>:目标路径,即正在创建的 image的文件系统路径;建议为 <dest>使用绝对路径,<dest>绝对路径为镜像中的路径,而不是宿主机的路径。否则, COPY指定则以 WORKDIR为其起始路径
注意:在路径中有空白字符时,通常使用第二种格式

文件复制准则 <src> 必须是build上下文中的路径,即只能放在workshop这个工作目录下,不能是其父目录中的文件 如果 <src> 是目录,其内部文件或者子目录会被递归复制,但 <src> 目录自身不会被复制 如果指定了多个 <src> ,或在 <src> 中使用了通配符,则 <dest> 必须是一个目录,且dest目录必须以/结尾 如果 <dest> 事先不存在,它将会被自动创建,这包括其父目录路径

EXPOSE

功能为暴漏容器运行时的监听端口给外部

但是EXPOSE并不会使容器访问主机的端口

如果想使得容器与主机的端口有映射关系,必须在容器启动的时候加上 -P参数

指令:EXPOSE 
功能描述:设置镜像暴露端口,记录容器启动时监听哪些端口 
语法:EXPOSE < port> < port> … 
延伸:镜像暴露端口可以通过docker inspect查看 
提示:容器启动时,Docker Daemon会扫描镜像中暴露的端口,如果加入-P参数,Docker Daemon会把镜像中所有暴露端口导出,并为每个暴露端口分配一个随机的主机端口(暴露端口是容器监听端口,主机端口为外部访问容器的端口) 
注意:EXPOSE只设置暴露端口并不导出端口,只有启动容器时使用-P/-p才导出端口,这个时候才能通过外部访问容器提供的服务

ENV

功能为设置环境变量

指令:ENV 
功能描述:设置镜像中的环境变量 
语法:ENV < key>=< value>…|< key> < value> 
注意:环境变量在整个编译周期都有效,第一种方式可设置多个环境变量,第二种方式只设置一个环境变量 
提示:通过${变量名}或者 $变量名使用变量,使用方式${变量名}时可以用${变量名:-default} ${变量名:+cover}设定默认值或者覆盖值 
          ENV设置的变量值在整个编译过程中总是保持不变的

在Dockerfile中使用变量的方式

  • $varname
  • ${varname}
  • ${varname:-default value}
  • $(varname:+default value} 第一种和第二种相同 第三种表示当变量不存在使用-号后面的值 第四种表示当变量存在时使用+号后面的值(当然不存在也是使用后面的值)

RUN

功能为运行指定的命令

指令:RUN 
功能描述: 
语法:RUN < command> 
     RUN [“executable”,”param1”,”param2”] 
提示:RUN指令会生成容器,在容器中执行脚本,容器使用当前镜像,脚本指令完成后,Docker Daemon会将该容器提交为一个中间镜像,供后面的指令使用 
补充:RUN指令第一种方式为shell方式,使用/bin/sh -c < command>运行脚本,可以在其中使用\将脚本分为多行 
     RUN指令第二种方式为exec方式,镜像中没有/bin/sh或者要使用其他shell时使用该方式,其不会调用shell命令 
例子:RUN source $HOME/.bashrc;\ 
     echo $HOME
     RUN [“/bin/bash”,”-c”,”echo hello”]
     RUN [“sh”,”-c”,”echo”,”$HOME”] 使用第二种方式调用shell读取环境变量

CMD

功能为容器启动时默认命令或参数

指令:CMD 
功能描述:设置容器的启动命令 
语法:CMD [“executable”,”param1”,”param2”] 
     CMD [“param1”,”param2”] 
     CMD < command> 
提示:CMD第一种、第三种方式和RUN类似,第二种方式为ENTRYPOINT参数方式,为entrypoint提供参数列表 
注意:Dockerfile中只能有一条CMD命令,如果写了多条则最后一条生效
补充细节:这里边包括参数的一定要用双引号,就是",不能是单引号。千万不能写成单引号。
原因是参数传递后,docker解析的是一个JSON array
CMD [ "sh", "-c", "echo $HOME" 
CMD [ "echo", "$HOME" ]
CMD /usr/sbin/init
CMD /usr/sbin/nginx -g "daemon off;"
不要把RUN和CMD搞混了。
RUN是构件容器时就运行的命令以及提交运行结果
CMD是容器启动时执行的命令,在构件时并不运行,构件时紧紧指定了这个命令到底是个什么样子

ENTRYPOINT

功能是:容器启动时运行的启动命令

指令:ENTRYPOINT 
功能描述:设置容器的入口程序 
语法:ENTRYPOINT [“executable”,”param1”,”param2”] 
     ENTRYPOINT command param1 param2(shell方式) 
第二种就是写shell
第一种就是可执行文件加参数
提示:入口程序是容器启动时执行的程序,docker run中最后的命令将作为参数传递给入口程序 
          入口程序有两种格式:exec、shell,其中shell使用/bin/sh -c运行入口程序,此时入口程序不能接收信号量 
          当Dockerfile有多条ENTRYPOINT时只有最后的ENTRYPOINT指令生效 
          如果使用脚本作为入口程序,需要保证脚本的最后一个程序能够接收信号量,可以在脚本最后使用exec或gosu启动传入脚本的命令 
注意:通过shell方式启动入口程序时,会忽略CMD指令和docker run中的参数 
          为了保证容器能够接受docker stop发送的信号量,需要通过exec启动程序;如果没有加入exec命令,则在启动容器时容器会出现两个进程,并且使用docker stop命令容器无法正常退出(无法接受SIGTERM信号),超时后docker stop发送SIGKILL,强制停止容器 
例子:FROM ubuntu <换行> ENTRYPOINT exec top -b

与CMD比较说明(这俩命令太像了,而且还可以配合使用):

相同点:

  • 只能写一条,如果写了多条,那么只有最后一条生效 容器启动时才运行,运行时机相同

不同点:

  • ENTRYPOINT不会被运行的command覆盖,而CMD则会被覆盖
  • 如果我们在Dockerfile种同时写了ENTRYPOINT和CMD,并且CMD指令不是一个完整的可执行命令,那么CMD指定的内容将会作为ENTRYPOINT的参数

如下:

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

如果我们在Dockerfile种同时写了ENTRYPOINT和CMD,并且CMD是一个完整的指令,那么它们两个会互相覆盖,谁在最后谁生效

如下:

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ls -al

那么将执行ls -al ,

top -b不会执行。

VOLUME

可实现挂载功能,可以将宿主机目录挂载到容器中

可用专用的文件存储当作Docker容器的数据存储部分

指令:VOLUME 
功能描述:设置容器的挂载点 
语法:VOLUME [“/data”] 
          VOLUME /data1 /data2 
提示:启动容器时,Docker Daemon会新建挂载点,并用镜像中的数据初始化挂载点,可以将主机目录或数据卷容器挂载到这些挂载点

一般的使用场景为需要持久化存储数据时

容器使用的是AUFS,这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失。

所以当数据需要持久化时用这个命令。

USER

设置启动容器的用户,可以是用户名或UID

指令:USER 
功能描述:设置RUN CMD ENTRYPOINT的用户名或UID 
语法:USER daemo
     USER UID

注意:如果设置了容器以daemon用户去运行,那么RUN, CMD 和 ENTRYPOINT 都会以这个用户去运行, 使用这个命令一定要确认容器中拥有这个用户,并且拥有足够权限

WORKDIR

设置工作目录,对RUN,CMD,ENTRYPOINT,COPY,ADD生效。如果不存在则会创建,也可以设置多次。

指令:WORKDIR 
功能描述:设置RUN CMD ENTRYPOINT ADD COPY指令的工作目录 
语法:WORKDIR < Path> 
提示:如果工作目录不存在,则Docker Daemon会自动创建 
Dockerfile中多个地方都可以调用WORKDIR,如果后面跟的是相对位置,则会跟在上条WORKDIR指定路径后(如WORKDIR /A   WORKDIR B   WORKDIR C,最终路径为/A/B/C)

WORKDIR也可以解析环境变量

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

pwd的执行结果是/path/$DIRNAME

ARG

指令:ARG 
功能描述:设置编译变量 
语法:ARG < name>[=< defaultValue>] 
注意:ARG从定义它的地方开始生效而不是调用的地方,在ARG之前调用编译变量总为空,在编译镜像时,可以通过docker build –build-arg < var>=< value>设置变量,如果var没有通过ARG定义则Daemon会报错 
          可以使用ENV或ARG设置RUN使用的变量,如果同名则ENV定义的值会覆盖ARG定义的值,与ENV不同,ARG的变量值在编译过程中是可变的,会对比使用编译缓存造成影响(ARG值不同则编译过程也不同) 
ARG CONT_IMAG_VER
RUN echo $CONT_IMG_VER 
ARG CONT_IMAG_VER
RUN echo hello 
当编译时给ARG变量赋值hello,则两个Dockerfile可以使用相同的中间镜像,如果不为hello,则不能使用同一个中间镜像

ONBUILD

指令:ONBUILD 
功能描述:设置自径想的编译钩子指令 
语法:ONBUILD [INSTRUCTION] 
提示:从该镜像生成子镜像,在子镜像的编译过程中,首先会执行父镜像中的ONBUILD指令,所有编译指令都可以成为钩子指令

STOPSIGNAL

STOPSIGNAL命令是的作用是当容器停止时给系统发送什么样的指令,默认是15

指令:STOPSIGNAL 
功能描述:设置容器退出时,Docker Daemon向容器发送的信号量 
语法:STOPSIGNAL signal 
提示:信号量可以是数字或者信号量的名字,如9或者SIGKILL,信号量的数字说明在Linux系统管理中有简单介绍

HEALTHCHECK

STOPSIGNAL signal

容器健康状况检查命令

语法有两种:

HEALTHCHECK [OPTIONS] CMD command
HEALTHCHECK NONE

第一个的功能是在容器内部运行一个命令来检查容器的健康状况

第二个的功能是在基础镜像中取消健康检查命令

[OPTIONS]的选项支持以下三中选项:

  • –interval=DURATION 两次检查默认的时间间隔为30秒
  • –timeout=DURATION 健康检查命令运行超时时长,默认30秒
  • –retries=N 当连续失败指定次数后,则容器被认为是不健康的,状态为unhealthy,默认次数是3

注意:

EALTHCHECK命令只能出现一次,如果出现了多次,只有最后一个生效。

CMD后边的命令的返回值决定了本次健康检查是否成功,具体的返回值如下:

  • 0: success - 表示容器是健康的
  • 1: unhealthy - 表示容器已经不能工作了
  • 2: reserved - 保留值

例子:

HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1

健康检查命令是:curl -f http://localhost/ || exit 1

两次检查的间隔时间是5秒

命令超时时间为3秒

补充一:ONBUILD流程

  • 编译时,读取所有ONBUILD镜像并记录下来,在当前编译过程中不执行指令
  • 生成镜像时将所有ONBUILD指令记录在镜像的配置文件OnBuild关键字中
  • 子镜像在执行FROM指令时会读取基础镜像中的ONBUILD指令并顺序执行,如果执行过程中失败则编译中断;当所有ONBUILD执行成功后开始执行子镜像中的指令
  • 子镜像不会继承基础镜像中的ONBUILD指令

补充二:CMD ENTRYPOINT和RUN的区别

RUN指令是设置编译镜像时执行的脚本和程序,镜像编译完成后,RUN指令的生命周期结束

容器启动时,可以通过CMD和ENTRYPOINT设置启动项,其中CMD叫做容器默认启动命令,如果在docker run命令末尾添加command,则会替换镜像中CMD设置的启动程序;ENRTYPOINT叫做入口程序,不能被docker run命令末尾的command替换,而是将command当作字符串,传递给ENTRYPOINT作为参数

FROM ubuntu
ENTRYPOINT ["ps"]
//通过命令docker run --rm test启动容器,打印ps的输出
//通过命令docker run --rm test -ef启动容器,打印ps -ef的输出

在docker run中,可以通过–entrypoint替换镜像中的入口程序,在Dockerfile中,应该至少有一条CMD或者ENTRYPOINT指令,如果同时定义了CMD和ENTRYPOINT则CMD会作为参数传递给ENTRYPOINT

FROM ubuntu
ENTRYPOINT ["ps"]
CMD ["-ef"]
//通过命令docker run --rm test启动容器,打印ps -ef的输出

原则与建议

  • 容器轻量化。从镜像中产生的容器应该尽量轻量化,能在足够短的时间内停止、销毁、重新生成并替换原来的容器。
  • 使用 .gitignore。在大部分情况下,Dockerfile 会和构建所需的文件放在同一个目录中,为了提高构建的性能,应该使用 .gitignore 来过滤掉不需要的文件和目录。
  • 为了减少镜像的大小,减少依赖,仅安装需要的软件包。
  • 一个容器只做一件事。解耦复杂的应用,分成多个容器,而不是所有东西都放在一个容器内运行。如一个 Python Web 应用,可能需要 Server、DB、Cache、MQ、Log 等几个容器。一个更加极端的说法:One process per container。
  • 减少镜像的图层。不要多个 Label、ENV 等标签。
  • 对续行的参数按照字母表排序,特别是使用yum install -y安装包的时候。
  • 使用构建缓存。如果不想使用缓存,可以在构建的时候使用参数--no-cache=true来强制重新生成中间镜像。

一张形象图



五、Dockerfile实战示例

5.1:构建 Nginx 镜像

5.1.1 通过nginx基础镜像构建自定义的nginx镜像

FROM nginx:1.21.0
RUN  rm -rf /usr/share/nginx/html/index.html 
COPY index.html /usr/share/nginx/html/index.html
COPY nginx.conf /etc/nginx/nginx.conf
COPY a.com.conf /etc/nginx/conf.d/a.com.conf
EXPOSE 80
STOPSIGNAL SIGQUIT
WORKDIR /
CMD ["nginx","-g","daemon off;"]

5.1.2 通过centos基础镜像构建自定义nginx镜像-yum安装nginx

# vim Dockerfile
FROM centos:7.9.2009
#用户信息
LABEL maintainer="this is nginx image "
# 配置epel仓库
ADD http://mirrors.aliyun.com/repo/epel-7.repo  /etc/yum.repos.d/epel.repo
#RUN yum install -y wger && wget -O /etc/yum.repos.d/epel.repo  http://mirrors.aliyun.com/repo/epel-7.repo
# yum安装nginx
RUN yum makecache fast && yum install -y nginx \
    && systemctl enable nginx \
    && rm -rf /usr/share/nginx/html/index.html \
    && echo "this is my test for new nginx" > /usr/share/nginx/html/index.html \
    && yum clean all
#指定工作目录
WORKDIR /
#指定http和https端口
EXPOSE 80  443
# 指定网站目录挂载点
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/sbin/nginx","-g","daemon off;"]

5.1.3 通过centos基础镜像构建自定义nginx镜像-编译nginx源码

# vim Dockerfile
FROM centos:7.9.2009
#用户信息
LABEL maintainer="this is nginx image "
# 设置nginx环境变量
ENV PATH /usr/local/nginx/sbin:$PATH
#添加环境包
RUN yum -y update && yum -y install pcre-devel zlib-devel gcc gcc-c++ make \
&& useradd -M -s /sbin/nologin nginx && mkdir -p /usr/local/nginx
#下载nginx软件包
ADD http://nginx.org/download/nginx-1.21.1.tar.gz  /usr/local/
# 本地拷贝压缩包
# COPY nginx-1.21.1.tar.gz  /usr/local/
# 工作目录
WORKDIR /usr/local/
# 解压缩并编译安装nginx
RUN tar -xf nginx-1.21.1.tar.gz -C /usr/local/ && cd nginx-1.21.1 &&./configure \
--prefix=/usr/local/nginx \
--user=nginx \
--group=nginx \
&& make && make install && echo "this is my_nginx" > /usr/local/nginx/html/index.html\
&& yum clean all
#指定工作目录
WORKDIR /
#指定http和https端口
EXPOSE 80  443
# 指定网站目录挂载点
VOLUME ["/usr/local/nginx/html"]
# ENTRYPOINT ["/sbin/entrypoint.sh"]
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]

5.2 构建mysql镜像

在线下载版:

FROM centos:7.9.2009
ENV  PATH=/usr/local/mysql/bin:/usr/local/mysql/lib:$PATH  \ 
     MYSQL_ROOT_PASSWORD=""
ADD https://mirrors.tuna.tsinghua.edu.cn/mysql/downloads/MySQL-5.7/mysql-5.7.33-el7-x86_64.tar.gz  \ /usr/local/
ADD https://mirrors.tuna.tsinghua.edu.cn/mysql/downloads/MySQL-5.7/mysql-boost-5.7.33.tar.gz  \
/usr/local/
ADD  enterpoint.sh         /usr/bin/
ADD  setmysqlpassword.sh   /usr/local/
# 安装依赖及前置环境
RUN yum -y install wget gcc gcc-c++ cmake make ncurses bison  openssl-devel \
    ncurses-devel zlib-devel bzip2 git \
    && cd /usr/local/ \
    && mkdir -p /usr/local/mysql/data \
    && mv  /usr/local/mysql-5.7.33  /usr/local/mysql-5.7.33-el7-x86_64/boost \
    && useradd -M -s /sbin/nologin  mysql
# 指定工作目录
WORKDIR /usr/local/mysql-5.7.33-el7-x86_64/boost/
# 编译安装mysql
RUN cmake . \
-DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
-DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \
-DSYSCONFDIR=/etc \
-DSYSTEMD_PID_DIR=/usr/local/mysql \
-DDEFAULT_CHARSET=utf8  \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_EXTRA_CHARSETS=all \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_ARCHIVE_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITH_PERFSCHEMA_STORAGE_ENGINE=1 \
-DMYSQL_DATADIR=/usr/local/mysql/data \
-DWITH_BOOST=/usr/local/mysql-5.7.33-el7-x86_64/boost/boost/boost_1_59_0 \
-DWITH_SYSTEMD=1 && make -j2&& make install && rm -rf /usr/local/mysql-5.7.33-el7-x86_64
RUN chown -R mysql:mysql /usr/local/mysql/  && rm -rf /etc/my.cnf
ADD my.cnf /etc
RUN chown mysql:mysql /etc/my.cnf
ENV PATH=/usr/local/mysql/bin:/usr/local/mysql/lib:$PATH
WORKDIR /usr/local/mysql/
RUN bin/mysqld \
--initialize-insecure \
--user=mysql \
--basedir=/usr/local/mysql \
--datadir=/usr/local/mysql/data
# 授权,必须授权否则脚本无法运行
RUN chmod 777 /usr/bin/enterpoint.sh && chmod 777 /usr/local/setmysqlpassword.sh 
EXPOSE 3306
WORKDIR /
ENTRYPOINT ["/usr/bin/enterpoint.sh"]  
# CMD ["/bin/bash","-c","/usr/bin/enterpoint.sh"]

本地上传版:

FROM centos:7.9.2009
ENV  MYSQL_ROOT_PASSWORD="" 
ADD  enterpoint.sh  /usr/bin/
ADD  setmysqlpassword.sh   /usr/local/
ADD mysql-5.7.33-el7-x86_64.tar.gz  /usr/local/
ADD mysql-boost-5.7.33.tar.gz  /usr/local/
# 安装依赖及前置环境
RUN yum -y install wget gcc gcc-c++ cmake make ncurses bison  openssl-devel \
    ncurses-devel zlib-devel bzip2 git \
    && cd /usr/local/ \
    && mkdir -p /usr/local/mysql/data \
    && mv  /usr/local/mysql-5.7.33  /usr/local/mysql-5.7.33-el7-x86_64/boost \
    && useradd -M -s /sbin/nologin  mysql
# 指定工作目录
WORKDIR /usr/local/mysql-5.7.33-el7-x86_64/boost/
# 编译安装mysql
RUN cmake . \
-DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
-DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \
-DSYSCONFDIR=/etc \
-DSYSTEMD_PID_DIR=/usr/local/mysql \
-DDEFAULT_CHARSET=utf8  \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_EXTRA_CHARSETS=all \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_ARCHIVE_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITH_PERFSCHEMA_STORAGE_ENGINE=1 \
-DMYSQL_DATADIR=/usr/local/mysql/data \
-DWITH_BOOST=/usr/local/mysql-5.7.33-el7-x86_64/boost/boost/boost_1_59_0 \
-DWITH_SYSTEMD=1 && make -j2&& make install && rm -rf /usr/local/mysql-5.7.33-el7-x86_64
RUN chown -R mysql:mysql /usr/local/mysql/  && rm -rf /etc/my.cnf
ADD my.cnf /etc
RUN chown mysql:mysql /etc/my.cnf
ENV PATH=/usr/local/mysql/bin:/usr/local/mysql/lib:$PATH
WORKDIR /usr/local/mysql/
RUN bin/mysqld \
--initialize-insecure \
--user=mysql \
--basedir=/usr/local/mysql \
--datadir=/usr/local/mysql/data
# 授权,必须授权否则脚本无法运行
RUN chmod 777 /usr/bin/enterpoint.sh && chmod 777 /usr/local/setmysqlpassword.sh 
EXPOSE 3306
WORKDIR /
ENTRYPOINT ["/usr/bin/enterpoint.sh"]  
# CMD ["/bin/bash","-c","/usr/bin/enterpoint.sh"]
vim enterpoint.sh
#!/bin/bash
# 启动mysql
function startmysql(){
    # 根据环境变量修改mysql初始密码
    file_script="/usr/local/setmysqlpassword.sh"
    if [ -f ${file_script} ]
            # 启动脚本设置mysql初始密码
            sh ${file_script}
            # 密码设置成功后删除改密码脚本
            if [ $? -eq 0 ] 
                    # 删除脚本
                    rm -rf ${file_script}
            # else
        # else 
    # 启动mysqld到前台
    mysqld --user=root
# 判断启动容器时是否传入环境变量,没有传入则无法启动容器
if [ "$MYSQL_ROOT_PASSWORD" == "" ]
# 启动函数
startmysql
setmysqlpassword.sh
#!/bin/bash
# 设置mysql初始密码
mysqld --user=root &
sleep 3
mysqladmin -u root  password $MYSQL_ROOT_PASSWORD
if [ $? -ne 0 ]
    #else
# 终止当前mysql进程
num=`ps axu | grep mysqld|grep -v grep|wc -l`
if [ ${num} -ne 0 ]
        ps axu | grep mysqld|grep -v grep| awk '{print $2}'|xargs  kill -9
    # else
# vim my.cnf
[client]
port = 3306
socket=/usr/local/mysql/mysql.sock          
[mysqld]
user = mysql
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
port = 3306
character-set-server=utf8
pid-file = /usr/local/mysql/mysqld.pid
socket=/usr/local/mysql/mysql.sock
bind-address = 0.0.0.0
skip-name-resolve
max_connections=2048
default-storage-engine=INNODB
max_allowed_packet=16M
server-id = 1
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_AUTO_VALUE_ON_ZERO,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,PIPES_AS_CONCAT,ANSI_QUOTES

rpm包yum安装版:

FROM centos:7.9.2009
ENV  MYSQL_ROOT_PASSWORD="" 
# 建议提前下好rpm包
ADD  http://mirrors.ustc.edu.cn/mysql-repo/yum/mysql-5.7-community/el/7/x86_64/mysql-community-client-5.7.25-1.el7.x86_64.rpm  /opt/mysql/
ADD  http://mirrors.ustc.edu.cn/mysql-repo/yum/mysql-5.7-community/el/7/x86_64/mysql-community-common-5.7.25-1.el7.x86_64.rpm  /opt/mysql/
ADD  http://mirrors.ustc.edu.cn/mysql-repo/yum/mysql-5.7-community/el/7/x86_64/mysql-community-libs-5.7.25-1.el7.x86_64.rpm        /opt/mysql/
ADD  http://mirrors.ustc.edu.cn/mysql-repo/yum/mysql-5.7-community/el/7/x86_64/mysql-community-server-5.7.25-1.el7.x86_64.rpm  /opt/mysql/  
ADD  enterpoint.sh   /usr/bin/
ADD  setmysqlpassword.sh /usr/local/
RUN  yum install -y net-tools.x86_64 libaio.x86_64 perl.x86_64 \
     && yum remove -y postfix.x86_64  mariadb-libs.x86_64 \
     && yum install -y /opt/mysql/*.rpm \
     && rm -rf /opt/mysql \
     && yum clean all \
     && mysqld --initialize-insecure  --user=root 
# 授权,必须授权否则脚本无法运行
RUN chmod 777 /usr/bin/enterpoint.sh && chmod 777 /usr/local/setmysqlpassword.sh 
EXPOSE 3306
WORKDIR /
ENTRYPOINT ["/usr/bin/enterpoint.sh"]  
# CMD ["/bin/bash","-c","/usr/bin/enterpoint.sh"]
vim enterpoint.sh
#!/bin/bash
# 启动mysql
function startmysql(){
    # 根据环境变量修改mysql初始密码
    file_script="/usr/local/setmysqlpassword.sh"
    if [ -f ${file_script} ]
            # 启动脚本设置mysql初始密码
            sh ${file_script}
            # 密码设置成功后删除改密码脚本
            if [ $? -eq 0 ] 
                    # 删除脚本
                    rm -rf ${file_script}
            # else
        # else 
    # 启动mysqld到前台
    mysqld --user=root
# 判断启动容器时是否传入环境变量,没有传入则无法启动容器
if [ "$MYSQL_ROOT_PASSWORD" == "" ]
# 启动函数
startmysql
setmysqlpassword.sh
#!/bin/bash
# 设置mysql初始密码
mysqld --user=root &
sleep 3
mysqladmin -u root  password $MYSQL_ROOT_PASSWORD
if [ $? -ne 0 ]
    #else
# 终止当前mysql进程
num=`ps axu | grep mysqld|grep -v grep|wc -l`
if [ ${num} -ne 0 ]
        ps axu | grep mysqld|grep -v grep| awk '{print $2}'|xargs  kill -9
    # else
fi

5.3 构建httpd镜像

FROM centos:systemd
RUN yum -y install httpd; yum clean all;echo `date +%F-%H:%M:%S` > /var/www/html/index.html
# 复制网站首页文件
# ADD index.html /var/www/html/index.html
EXPOSE 80
CMD ["/usr/sbin/httpd","-D","FOREGROUND"]

5.4 构建tomcat镜像

拷贝文件到当前目录 jdk-8u281-linux-x64.tar.gz apache-tomcat-9.0.16.tar.gz
#基于基础镜像
FROM centos:7.9.2009
#用户信息
LABEL maintainer="the tomcat Project"
ADD jdk-8u281-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.44.tar.gz /usr/local/
RUN mv /usr/local/jdk1.8.0_281 /usr/local/java  \
    && mv /usr/local/apache-tomcat-9.0.44 /usr/local/tomcat
ENV JAVA_HOME=/usr/local/java
ENV CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar  \
    PATH=$JAVA_HOME/bin:$PATH
EXPOSE 8080
CMD ["/usr/local/tomcat/bin/catalina.sh","run"]

5.5 构建ssh镜像

FROM centos:7.9.2009
LABEL maintainer="the SSH Project"
ENV ROOT_PASS_WORD=123456
RUN yum -y install openssh  openssh-server  openssh-clients iproute ;yum clean all
RUN echo ${ROOT_PASS_WORD} | passwd root --stdin 
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key \ 
    && ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key \
    && ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key
EXPOSE 22
CMD ["/usr/sbin/sshd","-D"]

5.6 构建systemctl镜像

FROM centos:7
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]

5.7 构建jenkins镜像

基于centos7基础镜像构建
FROM centos:7.9.2009
#用户信息
LABEL maintainer="the tomcat Project"
# 安装java8
ADD jdk-8u281-linux-x64.tar.gz /usr/local/
# 安装tomcat
ADD apache-tomcat-9.0.44.tar.gz /usr/local/
# 安装maven
ADD apache-maven-3.8.1-bin.tar.gz  /usr/local/
RUN mv /usr/local/jdk1.8.0_281 /usr/local/java  \
    && mv /usr/local/apache-tomcat-9.0.44 /usr/local/tomcat  \
    && mv /usr/local/apache-maven-3.8.1   /usr/local/maven
# 下载最新jenkins.war包
ADD https://mirrors.tuna.tsinghua.edu.cn/jenkins/war-stable/latest/jenkins.war  /usr/local/tomcat/webapps/
# 设置环境变量
ENV JAVA_HOME=/usr/local/java
ENV CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar  \
    MAVEN_HOME=/usr/local/maven
ENV PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH 
EXPOSE 8080
CMD ["/usr/local/tomcat/bin/catalina.sh","run"]
基于tomcat官方镜像tomcat:9.0.50-jdk8
FROM  tomcat:9.0.50-jdk8
# 安装maven
ADD  apache-maven-3.8.1-bin.tar.gz  /usr/local/
RUN  mv /usr/local/apache-maven-3.8.1 /usr/local/maven
ENV  MAVEN_HOME=/usr/local/maven
# 引用前面的定义的ENV必须另起一行
ENV  PATH=$MAVEN_HOME/bin:$PATH