企业——Dockerfile的编写及Dockerfile的优化

一.什么是Dockerfile?

  Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取 Dockerfile 中的指令自动生成映像。docker build 命令用于从 Dockerfile 构建映像。可以在 docker build 命令中使用 -f 标志指向文件系统中任何位置的Dockerfile。

  例如:docker build -f /path/to/a/Dockerfile

二.Dockerfile的基本结构和相应的命令说明

  Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,## 为 Dockerfile 中的注释信息。

  Docker以从上到下的顺序运行Dockerfile的指令。为了指定基本映像,第一条指令必须是FROM。注意:以#字符开头,被视为注释。可以在Docker文件中使用RUN(执行)、CMD(命令)、FROM(指定基本的映像信息)、EXPOSE、ENV等指令。

1. FROM:指定基础镜像,必须为第一个命令

  格式:   

    FROM <image>  FROM <image>:<tag>  FROM <image>@<digest>

  示例:

    FROM  mysql:5.6  ##注意:tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像

2.MAINTAINER: 维护者信息

  格式:

    MAINTAINER  <name>

  示例:

    MAINTAINER  wangfang  MAINTAINER  wang@qq.com  MAINTAINER  wangfang  <wang@qq.com>

3.RUN:构建镜像时执行的命令

  RUN用于在镜像容器中执行命令,具有两种命令执行的方式:

  (1)shell执行:RUN <command>

  (2)exec执行:RUN ["executable", "param1", "param2"]  示例:RUN ["/etc/execfile", "arg1", "arg1"]

4.ADD:将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源

  格式:

   ADD <src>... <dest>       ADD ["<src>",... "<dest>"]  ##用于支持包含空格的路径

  示例:

   ADD hom* /mydir/      ##添加所有以"hom"开头的文件

   ADD hom?.txt /mydir/      ## ? 替代一个单字符。例如:"home.txt"

   ADD test relativeDir/       ## 添加 "test" 到 `WORKDIR`/relativeDir/

   ADD test /absoluteDir/    ##添加 "test" 到 /absoluteDir/

5.COPY:功能类似ADD,但是是不会自动解压文件,也不能访问网络资源

6.CMD:构建容器后调用,也就是在容器启动时才进行调用。

  格式:

   CMD ["executable","param1","param2"] (执行可执行文件,优先)      CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)      CMD command param1 param2 (执行shell内部命令)

  示例:

   CMD echo "This is a test." | wc - CMD ["/usr/bin/wc","--help"]  ##注意:CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。

7.ENTRYPOINT:配置容器,使其可执行化。配合CMD可省去"application",只使用参数。

  格式:

  ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)

  ENTRYPOINT command param1 param2 (shell内部命令)

  示例:

  FROM ubuntu ENTRYPOINT ["top", "-b"] CMD ["-c"]    ##注意:ENTRYPOINT与CMD非常类似,不同的是通过 docker run 执行的命令不会覆盖ENTRYPOINT,而 docker run 命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令

8.ENV:设置环境变量

  格式:

  ENV <key> <value>    ##<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量

  ENV <key>=<value> ...    ##可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行

  示例:

   ENV myName wangfang      ENV myDog Rourou The Dog     ENV myCat=kitty

9.EXPOSE:指定于外界交互的端口

  格式:

   EXPOSE <port> [<port>...]

  示例:

   EXPOSE 80 443     EXPOSE 8080    EXPOSE 11211/tcp 11211/udp    ##注意:EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在 docker run 运行容器时通过 -p 来发布这些端口,或通过 -p 参数来发布EXPOSE导出的所有端口

10.WORKDIR:工作目录,类似于cd命令

  格式:

   WORKDIR /path/to/workdir    ## WORKDIR相当于linux中的命令 cd

  示例:

  WORKDIR /a (这时工作目录为/a)       WORKDIR b (这时工作目录为/a/b)      WORKDIR c (这时工作目录为/a/b/c)
  注意:通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。在使用 docker run 运行容器时,可以通过 -w 参数覆盖构建时所设置的工作目录。

11.VOLUME:可实现挂载功能,可以将内地文件夹或者其他容器种得文件夹挂在到这个容器种

   格式:

  volume  ["/data"]

  示例:

  volume  ["/avr/log/message"]

  volume  /var/log

  volume  /var/log  /etc/passwd

==========================================================================

 Dockerfile文件中CMD和EMTRYPOINT的区别?

  ENTRYPOINT 容器启动后执行的命令,让容器执行表现的像一个可执行程序一样,与CMD的区别是:ENTRYPOINT 不会被 docker run 覆 盖 , 会把 docker run 后面的参数,如:$name 等,当作传递给 ENTRYPOINT 指令的参数。Dockerfile 中只能指定一个 ENTRYPOINT,如果指定了很多,只有最后一个有效。docker run 命令的 -entrypoint 参数可以把指定的参数继续传递给 ENTRYPOINT。

  下面进行相应的测试,利用结果感觉这两个参数的不同:

(1)测试 ENTRYPOINT:

  docker load -i busybox.tar   ##导入一个镜像
  cd /tmp/docker/
  mkdir test
  cd test/
  pwd
    /tmp/docker/test
  vim Dockerfile
    FROM busybox
    ENV name world
    CMD echo "hello,$name"

  docker build -t busybox:v1 .    ##创建镜像
  docker run --rm busybox:v1    ##利用镜像创建新的容器
    hello,world

(2)测试CMD:

  vim Dockerfile
    FROM busybox
    ENV name world
    CMD ["/bin/echo","hello,$name"]


  docker build -t busybox:v2 .
  docker run --rm busybox:v2
    hello,$name

(3)使CMD不会被覆盖掉的方法

  vim Dockerfile
    FROM busybox
    ENV name world
    CMD ["/bin/sh","-c","echo hello, $name"]    ##这个命令在底层实际执行过程中,调用 /bin/sh -c  echo "hello world"

  docker build -t busybox:v3 .
  docker run --rm busybox:v3
    hello, world

  vim Dockerfile
    FROM busybox
    ENTRYPOINT ["/bin/echo", "hello"]
    CMD ["world"]
  docker build -t busybox:v4 .

  docker run --rm busybox:v4
    hello world
  docker run --rm busybox:v4 westos
    hello westos

==========================================================================

三.Dockerfile的编写  (这里安装的是HTTP服务)

1.导入镜像

  docker load -i rhel7.tar

2.编写相应的 Dockerfile 文件

  pwd
    /tmp/docker
  vim Dockerfile
    FROM rhel7    ##源镜像是rhel7,最好将名为rhel7的镜像放在本地

         ENV HOSTNAME server1
         COPY dvd.repo /etc/yum.repos.d/dvd.repo     ##仓库没有yum源,将真机的yum源cp到容器里
    RUN rpmdb --rebuilddb && yum install -y httpd && yum clean all  ##执行命令安装httpd并清除yum缓存,rpmdb 命令用于初始化和重建rpm数据库,--rebuilddb:从已安装的包头文件,反向重建RPM数据库
    EXPOSE 80      ##定义端口为80
    CMD ["/usr/share/httpd","-D","FOREGROUND"]  ### 打开apach服务, -D 是全局文件/etc/sysconfig/httpd中的打开参数

3.编写yum.repo(在上面的真机的路径下面配置相应的文件)

  vim yum.repo

    [rhel7.3]
    name=rhel7.3
    baseurl=http://172.25.254.1/rhel7.3    ##这里是网络yum源,本地的yum源用的是 file:/// 格式。
    gpgcheck=0

4.封装镜像,并测试能否正常使用

  之前都是用公共仓库中的镜像,直接用命令search 寻找相应的镜像,然后进行相应的拉取,最后直接利用镜像创建容器就可以了。现在使用的Dockerfile,就相当于是要用最基础的镜像配置(自定义)我们自己的镜像,然后也可以上传到私有仓库或者共有仓库当中。就相当于用刚才编写的 Dockerfile 文件所用的基础的镜像,在基础的镜像上添加了许多的新的应用,就比如这里添加的HTTP的功能,然后将这个新的添加了功能的这个容器封装成新的镜像,然后用新封装的镜像去创建容器,这样,这个创建的容器就可以,具有HTTP的服务。

  docker build -t rhel7:v1 . (注意后面 有个点表示当前目录)  ##使用当前目录的 Dockerfile 创建镜像,标签为 rhel7:v1

  docker run -d --name vm1 rhel7:v1    ##利用刚才新创建的封装的镜像创建需要HTTP的容器。新的镜像就相当于一个支持HTTP的镜像
  docker inspect vm1    ##查看新创建的容器vm1的信息,如:IP(172.17.0.2)
  cd /tmp/docker/
  ls
    Dockerfile dvd.repo
  vim index.html
    www.westos.org
  docker container cp index.html vm1:/var/www/html
  curl 172.17.0.2      ##可以正常访问使用
    www.westos.org

5.添加数据卷挂载位置(VOLUME [“var/www/html”])  (设定docker的某一个目录路径挂载在数据卷)

  vim Dockerfile
    FROM rhel7
    COPY dvd.repo /etc/yum.repos.d/dvd.repo
    RUN rpmdb --rebuilddb && yum clean all && yum install -y httpd 

    EXPOSE 80
    VOLUME ["var/www/html"]  ##将主机上的某一个目录或者路径下的文件挂载到docker容器上
    CMD ["/usr/sbin/httpd","-D","FOREGROUND"]

  docker build -t rhel7:v2 .  ##封装新的有挂载的镜像 rhel7:v2

  此时可以看到 rhel7:v2比 rhel7:v1多了一层: 利用 docker history 容器名  查看容器的每一层的实际的内容

      

  测试: (测试页面是否可以正常访问)

  mkdir webdata
  mv index.html webdata/
  ls
    Dockerfile webdata yum.repo

  docker rm -f vm1
  docker run -d --name vm1 -v /tmp/docker/webdata:/var/www/html rhel7:v2  ##如果指定挂载的路径,source就是指定的。如果没有指定的话,就像下面的操作一样,直接利用镜像创建了容器,挂载的目录路径是默认的。可以用 inspect 查看
    a8203a3b35eeeec170b7283fbbda136cffebc180a41c49891d3dd80c99f0e8ed
  curl 172.17.0.2     ##可以正常的访问
    www.westos.org

6.挂载修改挂载文件,发布目录内容相应改变  (在没有特定的设置挂载目录的情况下)

  直接在数据卷中修改:docker rm -f vm1    docker run -d --name vm1 rhel7:v2

  查看vm1的数据卷的位置:

      

  进入数据卷位置并编写发布文件并测试:

  cd /var/lib/docker/volumes/f03725ca02f048a96dc78ed02f3c9b1c29b198458d99a0765e7d51d56d22def9/_data
  cp /tmp/docker/webdata/index.html   .
  ls
    index.html
  curl 172.17.0.2
    www.westos.org

  echo hello,world >> index.html
  cat index.html
    www.westos.org
    hello,world
  curl 172.17.0.2
    www.westos.org
    hello,world

7.设定的只读挂载

   docker rm -f vm1  ##因为设置的不同,需要将之前的容器删除。

  docker run -d --name vm1 -v /tmp/docker/webdata:/data:ro rhel7:v2  ##指定了docker挂载的目录路径,而且设置了只读挂载 ro

  docker inspect vm1    ##找到source对应的数据卷位置
    /var/lib/docker/volumes/267d70d0fec9229a426c4e14f83b614ae4bc5acdbc2723862e0af006a78a2ec9/_data


  docker volume rm f03725ca02f048a96dc78ed02f3c9b1c29b198458d99a0765e7d51d56d22def9  ##删除之前的数据卷

  docker exec -it vm1 bash
    bash-4.2# cd data/
    bash-4.2# ls
      index.html
    bash-4.2# rm -fr index.html
      rm: cannot remove 'index.html': Read-only file system      ##删除文件被拒,显示只读Read-only
    bash-4.2# cat index.html      ##可以正常读取
      www.westos.org

四.Dockerfile优化之前的配置工作

        这里以搭建nginx的镜像为例

1.安装相应的安装包

  ls
    Dockerfile nginx-1.15.8.tar.gz test webdata yum.repo

2.编写相应的Dockerfile

  vim Dockerfile     ## 没有优化之前的Dockerfile文件
    FROM rhel7
    COPY dvd.repo /etc/yum.repos.d/dvd.repo
    RUN rpmdb --rebuilddb && yum install -y gcc pcre-devel zlib-devel make   ##解决依赖性,相关的应用的下载 
    ADD  nginx-1.15.8.tar.gz  /mnt    ##ADD比COPY更强大,如果文件是可识别的压缩文件,会帮忙解压
    WORKDIR /mnt/nginx-1.15.8  ##相当于 cd 到刚才解压以后的目录里面,然后执行下面的操作
    RUN sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc    ##关闭debug日志
    RUN ./configure --prefix=/usr/local/nginx    ##源码编译生产相应的makefile
    RUN make
    RUN make install
    EXPOSE 80
    VOLUME ["/usr/local/nginx/html"]
    CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]

3.利用 docker build封装nginx的镜像,并创建nginx容器

  docker build -t rhel7:v3 .

  docker rm -f vm1
  docker run -d --name vm1 rhel7:v3

4.测试nginx是否可以正常访问

  docker inspect vm1    ##查看 source 的绝对路径,和容器vm1的分配到的IP
    /var/lib/docker/volumes/2e9222ac2ddb8c79ea4391a9c4e6105dad6065cfcf2aa0f25c29273b9036769c/_data

  cd /var/lib/docker/volumes/2e9222ac2ddb8c79ea4391a9c4e6105dad6065cfcf2aa0f25c29273b9036769c/_data
  ls
    50x.html index.html
  echo "wangfang" > index.html
  cat index.html
    wangfang
  curl 172.17.0.2      #可以正常访问
    wangfang

5.优化前,镜像大小为276MB

      

五.Dockerfile的相关优化

1.第一次进行优化

(1)将不想看到的输出都导入到垃圾箱,可以减小文件的大小  

  vim Dockerfile
    FROM rhel7
    COPY yum.repo /etc/yum.repos.d/yum.repo
    RUN rpmdb --rebuilddb && yum install -y gcc pcre-devel zlib-devel make &> /dev/null && yum clean all
    ADD nginx-1.15.8.tar.gz /mnt
    WORKDIR /mnt/nginx-1.15.8
    RUN sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc
    RUN ./configure --prefix=/usr/local/nginx &> /dev/null
    RUN make &> /dev/null
    RUN make install &> /dev/null
    RUN rm -fr /mnt/nginx-1.15.8
    EXPOSE 80
    VOLUME ["/usr/local/nginx/html"]
    CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]

(2).重新封装一个镜像

  docker build -t rhel7:v4 .
    Successfully built fba2f9ad94cd
    Successfully tagged rhel7:v4

(3)查看新封装的镜像的大小,和之前的相互比较  (镜像的大小减小了,变为252MB)

      

2.第二次进行优化

(1)将RUN都放在一行,减少层数

  vim Dockerfile
    FROM rhel7
    COPY yum.repo /etc/yum.repos.d/yum.repo
    ADD nginx-1.15.8.tar.gz /mnt
    WORKDIR /mnt/nginx-1.15.8
    RUN rpmdb --rebuilddb && yum install -y gcc pcre-devel zlib-devel make &> /dev/null && yum clean all && sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc && ./configure --prefix=/usr/local/nginx &> /dev/null && make &> /dev/null && make install &> /dev/null && rm -fr /mnt/nginx-1.15.8
    EXPOSE 80
    VOLUME ["/usr/local/nginx/html"]
    CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]

(2)重新封装一个nginx的镜像

  docker build -t rhel7:v5 .

(3)查看新封装的镜像的大小

  docker images rhel7    ##镜像有减小,但是减小的不太明显

      

3.第三次进行优化  分阶段构建

(1)先将上面的 FROM rhel7 as build 利用 docker build 封装成标签为 latest 的镜像,然后下面的 FROM 会使用上面的标签的镜像进行接着封装镜像。就相当于新封装的镜像,只有141-140=1MB的大小。对于nginx之中,安装过程中会有一些输出的过程、二进制文件、安装相应的gcc等的依赖。到最后都用不到,能够使用到的就是那个1MB的安装好的,只需要将这1MB独自安装成镜像

  vim Dockerfile
    FROM rhel7 as build
    COPY yum.repo /etc/yum.repos.d/yum.repo
    ADD nginx-1.15.8.tar.gz /mnt
    WORKDIR /mnt/nginx-1.15.8
    RUN rpmdb --rebuilddb && yum install -y gcc pcre-devel zlib-devel make &> /dev/null && yum clean all && sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g"/g' auto/cc/gcc && ./configure --prefix=/usr/local/nginx &> /dev/null && make &> /dev/null && make install &> /dev/null && rm -fr /mnt/nginx-1.15.8

    FROM rhel7
    COPY --from=build /usr/local/nginx /usr/local/nginx
    EXPOSE 80
    CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]

(2)创建新的镜像

  docker build -t rhel7:v6 .

(3)利用新的镜像创建docker容器

       

4.最终极的镜像优化  (从底层开始的优化)

(1)因为只需要安装nginx只需要能够提供相应的底层的动态库就可以了,因此底层的镜像可以进一步的进行缩减。利用较小的大小的底层镜像。

  docker load -i distroless.tar
  docker load -i nginx.tar

  vim Dockerfile 

    FROM nginx as base    ##https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

    ARG Asia/Shanghai

    RUN mkdir -p /opt/var/cache/nginx &&
      cp -a --parents /usr/lib/nginx /opt &&
      cp -a --parents /usr/share/nginx /opt &&
      cp -a --parents /var/log/nginx /opt &&
      cp -aL --parents /var/run /opt &&
      cp -a --parents /etc/nginx /opt &&
      cp -a --parents /etc/passwd /opt &&
      cp -a --parents /etc/group /opt &&
      cp -a --parents /usr/sbin/nginx /opt &&
      cp -a --parents /lib/x86_64-linux-gnu/libpcre.so.* /opt &&
      cp -a --parents /lib/x86_64-linux-gnu/libz.so.* /opt &&
      cp -a --parents /lib/x86_64-linux-gnu/libc.so.* /opt &&
      cp -a --parents /lib/x86_64-linux-gnu/libdl.so.* /opt &&
      cp -a --parents /lib/x86_64-linux-gnu/libpthread.so.* /opt &&
      cp -a --parents /lib/x86_64-linux-gnu/libcrypt.so.* /opt &&
      cp -a --parents /usr/lib/x86_64-linux-gnu/libssl.so.* /opt &&
      cp -a --parents /usr/lib/x86_64-linux-gnu/libcrypto.so.* /opt &&
      cp /usr/share/zoneinfo/${TIME_ZONE:-ROC} /opt/etc/localtime

      FROM gcr.io/distroless/base

    COPY --from=base /opt /

    EXPOSE 80

    ENTRYPOINT ["nginx", "-g", "daemon off;"]

(2)创建新的镜像

  docker build -t rhel7:v7 .

(3)利用镜像创建新的docker容器

  docker run -d --name vm1 rhel7:v7

(4)测试:查看该镜像是否能够使用

  docker run -d --name vm1 rhel7:v7

  在网页的页面上输入:172.17.0.2

      

原文地址:https://www.cnblogs.com/wf-aiyouwei/p/10778910.html