zoukankan      html  css  js  c++  java
  • 6. Dockerfile详解

    镜像的生成途径

    About Dockerfile

    Dockerfile 就是构建docker镜像的源码,Dockerfile 是纯文本文件。

    基于Dockerfile制作docker镜像时,必须在特点的某个目录。Dockerfile 首字母必须大写。如果要打包文件,该文件必须放置在当前工作目录下。如果忽视某些文件可以定义 .dockerignore配置文件。 最后使用 docker build 来创建docker 镜像。

    Dockerfile指令

    FROM

    • FROM 指令是最重要的一个且必须为Dockerfile文件开篇的第一个非注释行,用于为映像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境
    • 实践中,基准镜像可以是任何可用镜像文件,默认情况下,docker build 会在 docker 主机上查找指定的镜像文件,在其不存在时,则会从 Docker Hub Registy 上拉取所需的镜像文件
      • 如果找不到指定的镜像文件,docker build 会返回一个错误信息
    • Syntax
      • FROM [:] 或
      • FROM @
        • :指定作为 base image 的名称;
        • :base image的标签,为可选项,省略时默认为 latest

    MAINTANIER(depreacted)

    • 用于让Dockerfile 制作者提供本人的详细信息
    • Dockerfile 并不限制 MAINTAINER 指令可在出现的位置,但推荐将其放置于FROM 指令之后。
    • Syntax
      • MAINTANIER <author's detail>
        • <author's detail> 可是任何文本信息,但约定俗成的使用作者名称及邮件地址
        • MAINTANIER "hukey hukey@126.com"

    LABEL

    语法:

    LABEL <key>=<value> <key>=<value> <key>=<value> <key>=<value>
    

    COPY

    • 用于从 Docker主机复制文件至创建的新映像文件
    • Syntax
      • COPY ...
      • COPY ["", .. ""]
        • :要复制的源文件或目录,支持使用通配符
        • :目标路径,即正在创建的image的文件系统路径;建议为 使用绝对路径,否则,COPY 指定则以 WORKDIR 为其起始路径;
      • 注意:在路径中有空白字符时,通常使用第二种格式
    • 文件复制准则
      • 必须是build上下文中的路径,不能是其父目录中的文件
      • 如果是目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制
      • 如果指定了多个,或在中使用了通配符,则必须是一个目录,且必须以 / 结尾
      • 如果事先不存在,它将会被自动创建,这包括其父目录路径
    # 一行命令查看容器内的信息
    docker run --name tinyweb1 --rm tinyhttpd:v0.1-1 cat /data/web/html/index.html
    

    ADD

    • ADD指令类似于COPY指令,ADD支持使用TAR文件和URL路径

    • Syntax

      • ADD ...
      • ADD ["", ... ""]
    • 操作准则

      • 同 COPY指令
      • 如果为URL且不以 / 结尾,则 制定的文件将被下载并直接被创建为 ;如果 以 / 结尾,则文件名URL 制定的文件将被直接下载并保存为/ “tar -x” 命令;然而,通过URL 获取到的tar文件将不会自动展开;
      • 如果有多个,或其间接或直接使用了通配符,则必须是一个以 / 结尾的目录路径;如果 不以 / 结尾,则其被视作一个普通文件, 的内容将被直接写入到

    WORKDIR

    • 用于为Dockerfile 中所有的 RUN 、CMD、ENTRYPOINT、COPY 和 ADD 定制设定工作目录
    • Syntax
      • WORKDIR
        • 在Dockerfile文件中,WORKDIR 指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个WORKDIR 指令指定的路径
        • 另外,WORKDIR 也可调用由ENV 指定定义的变量
      • 例如
        • WORKDIR /var/log 当设定工作目录时,会自动创建该目录。
        • WORKDIR $STATEPATH

    VOLUME

    • 用于在image中创建一个挂载点目录,以挂载Docker host 上的卷或其他容器上的卷
    • Syntax
      • VOLUME
      • VOLUME [""]
    • 如果挂载点目录路径下此前在文件存在,docker run 命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中。

    EXPOSE

    • 用于为容器打开指定要监听的端口以实现与外部通信
    • Syntax
      • EXPOSE [/] [[/] ...]
    • EXPOSE 指令可一次指定多个端口,例如:
      • EXPOSE 11211/udp 11211/tcp

    注意:当需要开放多个端口时,建议在一行中定义,否则会多出来很多层级。

    ENV

    • 用于为镜像定义所需的环境变量,并可被Dockerfile 文件中位于其后的其他指令(如 ENV、ADD、COPY等)所调用
    • 调用格式为 $variable_name${variable_name}
    • Syntax
      • ENV
      • ENV = ...
    • 第一种格式中,之后的所有内容均会被视作其的组成部分,因此,一次只能设置一个变量;
    • 第二种格式可用一次设置多个变量,每个变量为一个“=” 的键值对,如果中包含空格,可以以反斜线( ) 进行转义,也可通过对加引号进行标识;另外,反斜线也可用于续行;
    • 定义多个变量时,建议使用第二种方式,以便在同一层中完成所有功能

    RUN

    • 用于指定 docker build 过程中运行的程序,其可以是任何命令
    • Syntax
      • RUN
      • RUN ["", "",""]
    • 第一种格式中, 通常是一个 shell 命令,且以 "/bin/sh -c" 来运行它,这意味着此进程在容器中的PID 不为 1,不能接收Unix信号,因此,当使用 docker stop 命令停止容器时,此进程接收不到 SIGTERM信号;
      • 第二种语法格式中的参数是一个JSON格式的数组,其中为要运行的命令,后面的为传递给命令的选项或参数;然而,此种格式指定的命令不会以 /bin/sh -c 来发起依赖于此shell特性的话,可以将其替换为类似下面的格式。
        • RUN ["/bin/sh","-c","<executable>","param1"]
    • 主意:json数组中,要使用双引号

    CMD

    • 类似与RUN指令,CMD指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同。
      • RUN 指令运行于映像文件构建过程中,而CMD 指令运行于基于Dockerfile 构建出的新映像文件启动一个容器时,
      • CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD 指定的命令其可以被docker run 的命令行选项所覆盖
    • 在 Dockerfile 中可以存在多个CMD指令,但仅最后一个会生效
    • Syntax
      • CMD
      • CMD ["", "", ""]或
      • CMD ["", ""]
    • 前两种语法格式的意义同RUN
    • 第三种则用于为 ENTRYPOINT 指令提供默认参数

    RUN 和 CMD 区别:

    RUN 是可以运行多次的,如果多个命令之间有关联关系,建议在一条RUN当中把多个命令写进来。

    • CMD 在 docker build 阶段生成镜像;
    • CMD 是定义一个镜像文件启动为容器时默认要运行的程序,而docker是用运行一个程序,所以CMD 一般只出现一次,CMD 可以出现多次,但是只有最后一个生效;
    • RUN 可以出现多次,而且逐一运行;
    • CMD 的用法在一些方面是不一样的,而且CMD 通常不会单独使用,而是结合 ENTRYPOINT 使用。

    附加知识点:
    用户启动并创建进程的接口就是 shell,这是用户能够与操作系统交互的接口,所以事实上打开命令行提示符,就相当于正在运行的一个shell进程,而后在命令行之下所创建的任何进程都应该属于shell的子进程,而且有些进程还可能直接占据当前shell 的终端设备,将进程送到后台去用 & 符号,加上 & 并不能剥离当前shell的关系,它的父进程依然是shell,如果退出了shell,这个进程依然会被关闭。要实现脱离shell,需要使用命令 nohup 这个命令就表示脱离shell,运行到后台。

    在系统进程领域中,从来都是白发人送黑发人。当父进程退出时,一定会把它所属的子进程全部kill,而后在退出。如果父进程意外结束,那么它的子进程就成了孤儿进程,孤儿进程既不会释放内存,也不会终止了。

    关键命令:exec

    ENTRYPOINT

    • 类似 CMD 指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序
    • 与CMD 不同的是,由 ENTRYPOINT 启动的程序不会被 docker run 命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给 ENTRYPOINT 指定的程序
      • 不过,docker run 命令的 --entrypoint 选项的参数可覆盖 ENTRYPOINT 指令指定的程序
    • Syntax
      • ENTRYPOINT
      • ENTRYPOINT ["","",""]
    • docker run 命令传入的命令参数会覆盖CMD 指令的内容并且附加到 ENTRYPOINT 命令最后作为其参数使用
    • Dockerfile 文件中也可以存在多个 ENTRYPOINT 指令,但仅有最后一个会生效

    在创建容器时,可通过命令行中定义的命令来覆盖CMD的指令,但是有时候不允许覆盖,CMD 做不到,而 ENTRYPOINT 可以。

    在Dockerfile中,如果使用的是 ENTRYPOINT 而不是CMD,定义的命令是不会被覆盖的。

    [root@docker ~/img2]#cat Dockerfile 
    FROM busybox
    LABEL maintainer="hukey<hukey@super.com>" app="httpd"
    ENV WEB_DOC_ROOT="/data/web/html/"
    RUN mkdir -p ${WEB_DOC_ROOT} && 
    echo "<h1>busybox httpd server</h1>" > ${WEB_DOC_ROOT}/index.html
    
    #CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
    #CMD ["/bin/sh","-c","/bin/httpd","-f","-h /data/web/html/"]
    ENTRYPOINT /bin/httpd -f -h ${WEB_DOC_ROOT}
    
    [root@docker ~/img2]#docker run --name web2 --rm -P tinyhttpd:v0.2-5 ls /data/web/html/
    

    在Dockerfile 中可以定义多个 CMD ,只有最后一个生效。如果 ENTRYPOINT 定义了多个,只有最后一个生效。

    如果 CMD 和 ENTRYPOINT 同时定义,CMD 的参数将当作参数传递给 ENTRYPOINT 。多数情况下,ENTRYPOINT是指定一个shell。

    容器接收配置要靠环境变量。接下来通过一个示例来说明 CMD 和 ENTRYPOINT 连用。

    假如运行一个 nginx,要为nginx 提供一个配置文件要如何实现,或让nginx灵活接受配置该如何实现?

    [root@docker ~/img3]#cat Dockerfile 
    FROM nginx:1.14-alpine
    LABEL maintainer="hukey<hukey@super.com>"
    
    ENV NGX_DOC_ROOT='/data/web/html/'
    ADD index.html ${NGX_DOC_ROOT}
    ADD entrypoint.sh /bin/
    
    CMD ["/usr/sbin/nginx","-g","daemon off;"]
    ENTRYPOINT ["/bin/entrypoint.sh"]
    
    ------
    
    [root@docker ~/img3]#cat entrypoint.sh 
    #!/bin/sh
    # Author:hukey
    cat > /etc/nginx/conf.d/www.conf << EOF
    server {
      server_name $HOSTNAME;
      listen ${IP-0.0.0.0}:${PORT-80};
      root ${NGX_DOC_ROOT-/usr/share/nginx/html};
    }
    EOF
    
    exec "$@"
    

    USER

    • 用于指定运行image时的或运行Dockerfile中任何RUN 、CMD或ENTRYPOINT 指令指定的程序时的用户名或UID
    • 默认情况下,container的运行身份为 root 用户
    • Syntax
      • USER |
      • 需要注意的是,可以为任意数字,但实践中其必须为 /etc/passwd 中某用户的有效UID,否则,docker run 命令将运行失败

    HEALTHCHECK

    当一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy

    HEALTHCHECK 支持下列选项:

    • --interval=<间隔>:两次健康检查的间隔,默认为30秒
    • --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康价差就被视为失败,默认30秒
    • --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认3次。
    • --start-period=<时长>:定义容器启动多少秒之后再进行检测,默认 0 秒

    当使用 HEALTHCHECK 时,会返回三种状态:
    0:success 状态检测成功

    1:unhealthy 状态检测失败

    2:reserved 预留,没意义

    示例:

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

    和 CMD、ENTRYPOINT 一样,HEALTHCHECK 只能出现一次,如果写了多个,只有最后一个生效。

    [root@docker ~/img3]#cat Dockerfile 
    FROM nginx:1.14-alpine
    LABEL maintainer="hukey<hukey@super.com>"
    
    ENV NGX_DOC_ROOT='/data/web/html/'
    ADD index.html ${NGX_DOC_ROOT}
    ADD entrypoint.sh /bin/
    
    EXPOSE 80/tcp
    
    HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/ || exit 1
    CMD ["/usr/sbin/nginx","-g","daemon off;"]
    ENTRYPOINT ["/bin/entrypoint.sh"]
    
    ---
    
    [root@docker ~/img3]#cat entrypoint.sh 
    #!/bin/sh
    # Author:hukey
    cat > /etc/nginx/conf.d/www.conf << EOF
    server {
      server_name $HOSTNAME;
      listen ${IP-0.0.0.0}:${PORT-80};
      root ${NGX_DOC_ROOT-/usr/share/nginx/html};
    }
    EOF
    
    exec "$@"
    

    ONBUILD

    • 用于在Dockerfile中定义一个触发器
    • Dockerfile用于build映像文件,此映像文件亦可作为 base image 被另一个Dockerfile用作FROM 指令的参数,并以之构建新的映像文件
    • 在后面的这个Dockerfile 中的FROM 指令在 build 过程中被执行时,将会 “触发”创建其base image 的Dockerfile 文件中的 ONBUILD 指令定义的触发器
    • Syntax
      • ONBUILD
    • 尽管任何指令都可注册成为触发器指令,但ONBUILD 不能自我嵌套,且不会触发FROM 和MAINTAINER 指令
    • 在ONBUILD 指令中使用ADD或COPY指令应该格外小心,因为新构建过程的上下文在缺少指定的源文件时会失败。
    ...
    ONBUILD ADD http://192.168.118.45/nginx-1.18.0.tar.gz /usr/local/src/
    ...
    

    构建Dockerfile 基本准则

    容器应该是短暂的

    通过 Dockerfile 构建的镜像所启动的容器应该尽可能短暂(ephemeral)。短暂意味着可以很快地启动并且终止。

    使用 .dockerignore 排除构建无关文件

    .dockerignore 语法与 .gitignore 语法一致。使用它排除构建无关的文件及目录

    使用多阶段构建

    多阶段构建可以有效减小镜像体积,特别是对于需编译语言而言,一个应用的构建过程往往如下:

    1. 安装编译工具
    2. 安装第三方库依赖
    3. 编译构建应用

    而在前两步会有大量的镜像体积冗余,使用多阶段构建可以避免这一问题。

    避免安装不必要的包

    减小体积,减少构建时间。

    一个容器只做一件事

    如一个 Web 应用将会包含三个部分,Web 服务,数据库与缓存。把他们解耦到多个容器中,方便横向扩展。如果你需要网络通信,则可以将他们至于一个网络下。

    减少镜像层数

    • 只有RUN、COPY、ADD 会创建层数,其他指令不会增加镜像的体积
    • 尽可能使用多阶段构建

    例如:

    # 正确写法
    RUN yum install wget net-tools node -y
    # 错误写法
    RUN yum install -y wget
    RUN yum install -y net-tools
    RUN yum install -y node
    

    将多行参数排序

    便于可读性以及不小心的重复装包

    RUN yum install -y wget 
    	net-tools 
    	node
    

    充分利用构建缓存

    在镜像的构建过程中 Docker 会遍历 Dockerfile 文件中的所有指令,顺序执行。对于每一条指令,Docker 都会在缓存中查找是否已存在可重用的镜像,否则会创建一个新的镜像。

    我们可以使用 docker build --no-cache 跳过缓存:

    • ADD 和 COPY 将会计算文件的 checksum 是否改变来决定是否利用缓存
    • RUN 仅仅查看命令字符串是否命中缓存,如 RUN apt-get -y update 可能会有问题

    Dockerfile 实例

    以下示例来自网络:

    构建 sshd 镜像

    # Dockerfile
    FROM centos:centos7
    LABEL maintainer="hukey<hukey@super.com>"
    RUN yum install -y openssh* net-tools iproute NetworkManager && 
        # mkdir -p /var/run/sshd && 
        echo "UseDNS no" >> /etc/ssh/sshd_config && 
        sed -i '/pam_loginuid.so/d' /etc/pam.d/sshd && 
        echo '123123' | passwd --stdin root && 
        /usr/bin/ssh-keygen -A && 
        yum clean all
    
    EXPOSE 22
    CMD ["/usr/sbin/sshd", "-D"]
    
    # 制作镜像
    docker build -t sshd:v0.1-1 ./
    
    # 创建容器
    docker run --name ssh_server -P --rm -d sshd:v0.1-1
    

    构建systemctl 镜像

    (基于上面ssh镜像构建)

    # Dockerfile
    [root@docker ~/img6]#cat Dockerfile 
    FROM sshd:v0.1-2
    LABEL maintainer="hukey<hukey@super.com>"
    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"]
    
    # 制作镜像
    [root@docker ~/img6]#docker build -t systemd:v0.1-1 ./
    
    # 创建容器
    [root@docker ~/img6]#docker run --name sys-1 -d --privileged -v /sys/fs/cgroup/:/sys/fs/cgroup/:ro systemd:v0.1-1 /sbin/init
    
    --privileged:privateged container 内的root拥有真正的root权限,否则,container内的root只是外部的一个普通用户权限。
    
    # 进入容器
    [root@docker ~/img6]#docker exec -it sys-1 /bin/bash
    
    # 通过 systemctl 查看sshd状态
    [root@ffe6f1ec35d1 /]# systemctl status sshd
    ● sshd.service - OpenSSH server daemon
       Loaded: loaded (/usr/lib/systemd/system/sshd.service; disabled; vendor preset: enabled)
       Active: inactive (dead)
         Docs: man:sshd(8)
               man:sshd_config(5)
    

    构建 nginx 镜像

    [root@docker ~]#mkdir -p nginx_img
    [root@docker ~]#cd nginx_img/
    [root@docker ~/nginx_img]#cat Dockerfile 
    FROM centos:centos7
    LABEL maintainer="hukey<hukey@super.com>"
    ENV NGX_PACKAGE="nginx-1.18.0"
    #ADD http://nginx.org/download/${NGX_PACKAGE}.tar.gz /usr/local/src/
    ADD http://192.168.118.45/${NGX_PACKAGE}.tar.gz /usr/local/src/
    WORKDIR /usr/local/src/${NGX_PACKAGE}
    RUN tar xf /usr/local/src/${NGX_PACKAGE}.tar.gz && 
        cd ${NGX_PACKAGE} && 
        yum install -y proc-devel gcc gcc-c++ zlib zlib-devel make openssl-devel wget && 
        ./configure --prefix=/usr/local/nginx 
        --with-http_ssl_module 
        --with-http_gzip_static_module 
        --with-http_stub_status_module 
        --with-http_realip_module && 
        make -j 2 && make install && 
        echo "daemon off;" >> /usr/local/nginx/conf/nginx.conf && 
        yum clean all && 
        rm -rf /var/cache/yum/*
    EXPOSE 80/tcp 443/tcp
    ADD run.sh /run.sh
    CMD ["/run.sh"]
    
    ---
    [root@docker ~/nginx_img]#cat run.sh 
    #!/bin/bash
    # Author:hukey
    /usr/local/nginx/sbin/nginx
    
    # 制作镜像
    [root@docker ~/nginx_img]#docker build -t nginx:v0.1-1 ./
    
    # 启动容器
    [root@docker ~/nginx_img]#docker run --name web1 --rm -d -P nginx:v0.1-1
    
    # 查看映射端口
    [root@docker ~/nginx_img]#docker port web1
    443/tcp -> 0.0.0.0:32788
    80/tcp -> 0.0.0.0:32789
    

    构建tomcat 镜像

    [root@docker ~/tomcat]#ls
    apache-tomcat-8.5.61.tar.gz  Dockerfile  jdk-8u77-linux-x64.tar.gz
    [root@docker ~/tomcat]#cat Dockerfile 
    FROM centos:centos7
    LABEL maintainer="hukey <hukey@super.com>"
    ADD jdk-8u77-linux-x64.tar.gz /usr/local/
    ADD apache-tomcat-8.5.61.tar.gz /usr/local/
    ENV JAVA_HOME="/usr/local/java" 
        JAVA_BIN="/usr/local/java/bin" 
        JRE_HOME="/usr/local/java/jre" 
        PATH="$PATH:/usr/local/java/bin:/usr/local/java/jre/bin" 
        CLASSPATH="/usr/local/java/jre/bin:/usr/local/java/lib:/usr/local/java/jre/lib/charsets.jar"
    WORKDIR /usr/local/
    RUN mv jdk1.8.0_77 java && 
        mv apache-tomcat-8.5.61 tomcat8 
    EXPOSE 8080
    ENTRYPOINT ["/usr/local/tomcat8/bin/catalina.sh", "run"]
    
    # 制作镜像
    [root@docker ~/tomcat]#docker build -t tomcat:v0.1-1 ./
    
    # 启动容器
    [root@docker ~/tomcat]#docker run --name tomcat-1 --rm -P -d tomcat:v0.1-1
    
    # 查看容器映射端口
    [root@docker ~/tomcat]#docker port tomcat-1
    8080/tcp -> 0.0.0.0:32796
    
  • 相关阅读:
    java中Executor、ExecutorService、ThreadPoolExecutor介绍
    JAVA多线程实现的四种方式
    JVM内存结构
    Synchronized修饰静态变量和普通变量的区别
    tcpkill工作原理分析
    数据库路由中间件MyCat
    数据库路由中间件MyCat
    数据库路由中间件MyCat
    数据库路由中间件MyCat
    数据库路由中间件MyCat
  • 原文地址:https://www.cnblogs.com/hukey/p/14111873.html
Copyright © 2011-2022 走看看