7.使用Dockerfile构建镜像

文章目录

Dockerfile简介

Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的配置文件脚本。Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程等等。

Dockerfile官网详解

创建镜像的方式以及镜像和容器的关系如下图所示:

构建三步骤:

  • 编写Dockerfile文件
  • docker build 命令构建镜像
  • docker run以镜像运行容器实例

基本结构

基础知识:

  1. 每条保留字指令都必须为大写字母 且后面要跟随至少一个参数

  2. 指令按照从上到下,顺序执行;

  3. #表示注释

  4. 每条指令都会创建一个新的镜像层并对镜像进行提交

Dockerfile主体内容分为四部分:

  1. 基础镜像信息
  2. 维护者信息
  3. 镜像操作指令
  4. 容器启动指令

Docker执行Dockerfile的流程:

  1. docker从基础镜像运行容器
  2. 执行一条指令并对容器进行修改
  3. 执行类似docker commit的操作提交一个新的镜像层
  4. docker再基于刚提交的镜像运行一个新容器
  5. 执行dockerfile中的下一条指令知道所有指令执行完成

Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段,

  • Dockerfile是软件的原材料
  • Docker镜像是软件的交付品
  • Docker容器则可以认为是软件镜像的运行态,也即依照镜像运行的容器实例

DockerFile常用保留字指令


FROM

指定一个已经存在的镜像作为模板,任何Dockerfile 中第一条指令必须为FROM指令(ARG除外)。如果在同一个Dockerfile中创建多个镜像时, 可以使用多个FROM 指令(每个镜像一次)。

格式:

FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

在Dockerfile中,ARG是唯一可能位于FROM之前的指令。

dockerfile 复制代码
ARG VERSION=9.3
FROM debian:${VERSION}

通过向FROM指令添加AS名,可以为新的构建阶段指定一个可选的名称。该名称可以在后续的FROM <name>、COPY --FROM =<name>和RUN --mount=type=bind,FROM =<name>指令中使用,以引用在此阶段构建的映像。

LABEL

LABEL指令可以为生成的镜像添加元数据标签信息。这些信息可以用来辅助过滤出特定镜像。

格式:

dockerfile 复制代码
LABEL <key>=<value> <key>=<value> <key>=<value> ...

要查看镜像的标签,使用docker image inspect命令。你可以使用--format选项只显示标签:

LABEL multi.label1="value1" multi.label2="value2" other="value3"

LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"
bash 复制代码
docker image inspect --format='{{json .Config.Labels}}' myimage

{
  "com.example.vendor": "ACME Incorporated",
  "com.example.label-with-value": "foo",
  "version": "1.0",
  "description": "This text illustrates that label-values can span multiple lines.",
  "multi.label1": "value1",
  "multi.label2": "value2",
  "other": "value3"
}

MAINTAINER(废弃)

镜像维护者的姓名和邮箱地址。

dockerfile 复制代码
MAINTAINER zhangyaning<zyn@qq.com>
#可以使用LABEL替代
LABEL org.opencontainers.image.authors="SvenDowideit@home.org.au"

RUN

容器构建时需要运行的命令,RUN时在docker build时运行。RUN指令将执行任何命令,在当前映像之上创建一个新层。添加的层在Dockerfile的下一步中使用。

格式:

# Shell form:
RUN [OPTIONS] <command> ...
# Exec form:
RUN [OPTIONS] [ "<command>", ... ]

shell格式

dockerfile 复制代码
RUN yum -y install vim

exec格式

RUN ["可执行文件","参数1","参数2"]

dockerfile 复制代码
RUN ["./test.php","dev","offline"]等价于 RUN ./test.php dev offline

exec指令会被解析为JSON数组,因此必须用双引号。shell格式默认将在shell终端中运行命令,即/bin/sh -c; 后者则使用exec执行,不会启动shell环境。

两者具体对比引用博客

https://blog.csdn.net/hetoto/article/details/99700608

https://blog.csdn.net/zhyysj01/article/details/115391518

每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像层。当命令较长时可以使用\来换行。例如:

RUN apt-get update \
	&& apt-get install -y libsnappy-dev \
	&& rm -rf /var/cache/apt \
	&& rm -rf /var/lib/apt/lists/*

EXPOSE

声明镜像内服务监听的端口。可以指定端口是侦听TCP还是UDP,如果不指定协议,则默认为TCP。

格式:

dockerfile 复制代码
EXPOSE <port> [<port>/<protocol>...]

⚠️注意该指令只是起到声明作用, 并不会自动完成端口映射。

如果要映射端口出来,在启动容器时可以使用平参数(Docker主机会自动分配一个宿主机的临时端口)或-p HOST_PORT:CONTAINER_PORT参数(具体指定所映射的本地端口)。

WORKDIR

指定在创建容器后,终端默认登陆进来的工作目录。

WORKDIR指令可以在Dockerfile中多次使用。如果提供了一个相对路径,它将相对于之前的WORKDIR指令的路径。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

在这个Dockerfile中,最终pwd命令的输出将是/a/b/c。

USER

指定该镜像以哪个用户执行,默认为root。

ENV

用来在构建镜像过程中设置环境变量,在镜像生成过程中会被后续RUN指令使用,在镜像启动的容器中也会存在。

可以使用docker inspect查看这些值,并使用docker run --env <key>=<value>覆盖或更改它们。

bash 复制代码
docker run --env <key>=<value> build_image

注意当一条ENV指令中同时为多个环境变量赋值并且值也是从环境变量读取时,会为变量都赋值后再更新。如下面的指令,最终结果为key1=value1 key2 =value2。

dockerfile 复制代码
ENV key1=value2
ENV key1=value1 key2=${key1}

ARG

定义创建镜像过程中使用的变量。

ARG <name>[=<default value>]

在执行docker build时,可以通过--build-arg <varname>=<value>来为变量赋值。当镜像编译成功后,ARG指定的变量不在存在(ENV指定的变量将在镜像中保留)。

Docker内置了一些镜像创建变量,用户可以直接使用无须声明,包括HTTP_PROXY、HTTPS_PROXY、FTP_PROXY、NO_PROXY(不区分大小写)。Dockerfile可以包含一个或多个ARG指令。

dockerfile 复制代码
FROM busybox
ARG user1
ARG buildno=1
# ...

ARG VERSION=9.3
FROM debian:${VERSION}

ADD

将宿主机目录下的文件拷贝进镜像且会自动处理URL和解压tar压缩包。

格式:

ADD <SrC> <dest>

该命令将复制指定的<SrC>路径下内容到容器中的<dest>路径下。

其中<SrC>可以是Dockerfile所在目录的一个相对路径(文件或目录);也可以是一个URL; 还可以是一个tar文件(自动解压为目录)<dest>可以是镜像内绝对路径, 或者相对于工作目录(WORKDIR)的相对路径。

路径支持正则格式, 例如:

dockerfile 复制代码
ADD *.c /code/

COPY

类似ADD,拷贝文件和目录到镜像中。目标路径不存在时, 会自动创建。

将从构建上下文目录中<源路径>的文件/目录复制新的一层镜像内的<目标路径>位置

dockerfile 复制代码
COPY src dest
COPY ["src","dest"]

在Dockerfile中,ADDCOPY都是用于将文件从构建上下文或URL复制到镜像中的指令。虽然这两个指令有相似的功能,但它们在使用上有一些关键的区别:

  1. 基本功能

    • COPY:基本上用于复制本地文件或目录到镜像中的指定路径,它只关注从构建上下文到镜像的简单复制。
    • ADD:除了具有COPY的功能外,还支持两个附加功能:自动解压缩压缩文件(如tar压缩包)到镜像中,以及可以从URL直接下载文件到镜像中。
  2. 使用场景

    • COPY应该是复制本地文件到Docker镜像时的首选指令,因为它的行为非常直接和清晰。
    • ADD更适用于需要自动解压缩文件或从远程URL下载文件的场景。例如,如果你想在构建过程中将一个tar文件的内容自动解压到镜像中,可以使用ADD
  3. 建议

    • Docker官方推荐尽可能使用COPY,因为它更透明。ADD的额外功能(如解压缩和支持URL)可能会引入意外的复杂性,比如不明确的来源和自动解压行为可能不是你想要的。
  4. 示例

    • 使用COPY:

      dockerfile 复制代码
      COPY ./localfolder /destinationfolder
    • 使用ADD:

      dockerfile 复制代码
      ADD http://example.com/big.tar.gz /var/www/html
      ADD ./archive.tar.gz /usr/src/app

虽然ADD提供了一些额外的功能,但在大多数情况下,推荐使用COPY以保持Dockerfile的清晰和易于维护。只有在特定需要利用ADD的自动解压缩或从URL下载文件的功能时,才考虑使用ADD

VOLUME

容器数据卷,用于数据保存和持久化工作。

格式为

dockerfile 复制代码
VOLUME ["/data"] 

运行容器时可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和需要持久化的数据等。

CMD

CMD指令的格式和RUN相似。

  • shell格式,等同于在终端操作的shell命令
dockerfile 复制代码
CMD yum -y install vim
  • exec格式

CMD ["可执行文件","参数1","参数2"]

dockerfile 复制代码
CMD ["./test.php","dev","offline"]等价于 RUN ./test.php dev offline
  • 参数列表格式

在指定了ENTRYPOINT指令后,用CMD指令具体的参数。

Dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run之后的参数替换

CMD与RUN的区别:

CMD是在docker run时运行

RUN是在docker build时运行

ENTRYPOINT

用来指定一个容器启动时要运行的命令。类似于CMD指令,但是ENTRYPOINT不会被docker run后面的命令覆盖,而且执行命令行参数会被当做参数送给ENTRYPOINT指令指定的程序。

命令格式:

dockerfile 复制代码
ENTRYPOINT ["<executeable>","<param1>","<param2>"]

支持两种格式:

  • ENTRYPOINT ["executable", "paraml " , "param2"]: exec调用执行;
  • ENTRYPOINT command param1 param2: shell中执行。(不推荐)

在Docker容器中,如果使用ENTRYPOINT的shell形式,这会带来几个问题:

  1. 阻止使用CMD命令行参数 :当你使用ENTRYPOINT的shell形式时,它会忽略任何CMD提供的命令行参数。这是因为在shell形式下,ENTRYPOINT指令指定的命令会被直接传递给/bin/sh -c来执行,而不会与CMD指令中的参数结合。
  2. 不作为容器的PID 1运行 :通常在Docker容器中,第一个启动的进程(PID 1)负责处理系统信号(例如SIGTERM和SIGKILL等)。但是,如果你使用ENTRYPOINT的shell形式,实际上/bin/sh -c成为了PID 1。这意味着你的应用程序实际上是作为/bin/sh -c的子进程启动的,而不是直接作为PID 1。
  3. 不接收Unix信号 :由于你的应用程序不是PID 1,并且是通过/bin/sh -c间接启动的,它不会直接接收到Unix信号。这包括由docker stop <container>命令发送的SIGTERM信号。/bin/sh -c通常不会将接收到的信号转发给它的子进程,因此你的应用可能无法正确响应停止命令,比如进行清理操作或正常关闭。

总结来说,使用ENTRYPOINT的shell形式可能导致Docker容器中的应用程序无法接收到重要的系统信号,也无法使用CMD提供的额外参数,这可能会影响应用程序的正常运行和优雅关闭。如果需要让你的应用程序能够正确处理信号并与CMD参数兼容,建议使用ENTRYPOINT的exec形式。这样,应用程序可以直接作为PID 1运行,并能接收到所有必要的系统信号。

ENTRYPOINT可以和CMD一起用,一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参。

当指定了ENTRYPOINT后,CMD的含义就发生了变化,不再是直接运行其命令而是将CMD的内容作为参数传递给ENTRYPOINT指令,他两个组合会变成:

<ENTRYPOINT> "CMD"

案例如下:假设已通过 Dockerfile 构建了 nginx:test 镜像:

dockerfile 复制代码
FROM nginx

ENTRYPOINT ["nginx","-c"] #定参
CMD ["/etc/nginx/nginx.conf"] #变参
是否传参 按照dockerfile编写执行 传参运行
Docker命令 docker run nginx:test docker run nginx:test -c /etc/nginx/new.conf
衍生出的实际命令 nginx -c /etc/nginx/nginx.conf nginx -c /etc/nginx/new.conf

优点:在执行docker run的时候可以指定ENTRYPOINT运行所需的参数。

注意:如果Dockerfile存在多个ENTRYPOINT指令,仅最后一个生效。

个人认为ENV也应放在Both列下。

使用Dockerfile构建镜像命令

编写完成Dockerfile之后,可以通过docker [image] build命令来创建镜像。

bash 复制代码
docker build [OPTIONS] PATH | URL | -

读取指定路径下(包括子目录)的Dockerfile, 并将该路径下所有数据作为上下文(Context)发送给Docker服务端。Docker服务端在校验Dockerfile格式通过后, 逐条执行其中定义的指令, 碰到ADD、COPY和RUN指令会生成一层新的镜像。最终如果创建镜像成功, 会返回最终镜像的ID。

如果上下文过大, 会导致发送大量数据给服务端, 延缓创建过程。因此除非是生成镜像所必需的文件, 不然不要放到上下文路径下。如果使用非上下文路径下的Dockerfile, 可以通过-f选项来指定其路径。

https://docs.docker.com/reference/cli/docker/image/build/

案例

使用Centos7作为基础镜像,构建具备vim、ifconfig、jdk8的镜像。

  1. 准备jdk包上传至节点

    bash 复制代码
    wget https://mirrors.yangxingzhen.com/jdk/jdk-8u181-linux-x64.tar.gz
  2. 准备Dockerfile文件

    dockerfile 复制代码
    FROM centos:7
    MAINTAINER zhangyaning<zyn@qq.com>
    
    ENV MYPATH /usr/local
    WORKDIR $MYPATH
    
    #安装vim编辑器
    RUN yum -y install vim
    #安装ifconfig命令查看网络IP
    RUN yum -y install net-tools
    #安装java8及lib库
    RUN yum -y install glibc
    RUN mkdir /usr/local/java
    #ADD 是相对路径jar,把jdk-8u401-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
    # 此处需要根据linux系统选择对应的压缩包
    ADD jdk-8u411-linux-aarch64.tar.gz /usr/local/java/
    #配置java环境变量
    ENV JAVA_HOME /usr/local/java/jdk1.8.0_411
    ENV JRE_HOME $JAVA_HOME/jre
    ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
    ENV PATH $JAVA_HOME/bin:$PATH
    
    EXPOSE 80
    
    CMD echo $MYPATH
    CMD echo "success--------------ok"
    CMD /bin/bash
  3. 构建镜像

    bash 复制代码
    [root@zyn01 ~]# docker build -t zyncentos:1.0 .
    [+] Building 16.6s (12/12) FINISHED                                                                                                docker:default
     => [internal] load build definition from Dockerfile                                                                                         0.0s
     => => transferring dockerfile: 835B                                                                                                         0.0s
     => [internal] load metadata for docker.io/library/centos:7                                                                                 15.3s
     => [internal] load .dockerignore                                                                                                            0.0s
     => => transferring context: 2B                                                                                                              0.0s
     => [1/7] FROM docker.io/library/centos:7@sha256:9d4bcbbb213dfd745b58be38b13b996ebb5ac315fe75711bd618426a630e0987                            0.0s
     => [internal] load build context                                                                                                            0.2s
     => => transferring context: 75.40MB                                                                                                         0.2s
     => CACHED [2/7] WORKDIR /usr/local                                                                                                          0.0s
     => CACHED [3/7] RUN yum -y install vim                                                                                                      0.0s
     => CACHED [4/7] RUN yum -y install net-tools                                                                                                0.0s
     => CACHED [5/7] RUN yum -y install glibc                                                                                                    0.0s
     => CACHED [6/7] RUN mkdir /usr/local/java                                                                                                   0.0s
     => [7/7] ADD jdk-8u411-linux-aarch64.tar.gz /usr/local/java/                                                                                0.8s
     => exporting to image                                                                                                                       0.2s
     => => exporting layers                                                                                                                      0.2s
     => => writing image sha256:c40d58dd6c5edb7ee1403565ba5657b290c5756e016f92af1b6d651adb0c8e0c                                                 0.0s
     => => naming to docker.io/library/zyncentos:1.0 
  4. 运行镜像

    bash 复制代码
    [root@zyn01 ~]# docker run -it zyncentos:1.0 /bin/bash
    [root@c5040cd7f1bb local]# java -version
    java version "1.8.0_411"
    Java(TM) SE Runtime Environment (build 1.8.0_411-b09)
    Java HotSpot(TM) 64-Bit Server VM (build 25.411-b09, mixed mode)

自定义zybubuntu镜像,安装ifconfig命令

  1. 编写Dockerfile文件

    dockerfile 复制代码
    FROM ubuntu
    MAINTAINER zyn<zyn@qq.com>
     
    ENV MYPATH /usr/local
    WORKDIR $MYPATH
     
    RUN apt-get update
    RUN apt-get install net-tools
    #RUN apt-get install -y iproute2
    #RUN apt-get install -y inetutils-ping
     
    EXPOSE 80
    
    CMD echo $MYPATH
    CMD echo "install inconfig cmd into ubuntu success--------------ok"
    CMD /bin/bash
  2. 构建镜像

    bash 复制代码
    [root@zyn01 ~]# cd zynubuntu/
    [root@zyn01 zynubuntu]# vim Dockerfile
    [root@zyn01 zynubuntu]# docker build -t zynubuntu:1.0 .
    [+] Building 659.2s (8/8) FINISHED                                                                                                 docker:default
     => [internal] load build definition from Dockerfile                                                                                         0.0s
     => => transferring dockerfile: 413B                                                                                                         0.0s
     => [internal] load metadata for docker.io/library/ubuntu:latest                                                                             0.0s
     => [internal] load .dockerignore                                                                                                            0.0s
     => => transferring context: 2B                                                                                                              0.0s
     => [1/4] FROM docker.io/library/ubuntu:latest                                                                                               0.0s
     => CACHED [2/4] WORKDIR /usr/local                                                                                                          0.0s
     => [3/4] RUN apt-get update                                                                                                               655.2s
     => [4/4] RUN apt-get install net-tools                                                                                                      3.9s
     => exporting to image                                                                                                                       0.1s 
     => => exporting layers                                                                                                                      0.1s 
     => => writing image sha256:8b41be3838d1994909f79cddd6a625ff02a32c7996818886cbeaae64237a7d1b                                                 0.0s 
     => => naming to docker.io/library/zynubuntu:1.0              
  3. 运行容器

    bash 复制代码
    [root@zyn01 zynubuntu]# docker images                                                                                                             
    REPOSITORY     TAG       IMAGE ID       CREATED         SIZE                                                                                      
    zynubuntu      1.0       8b41be3838d1   6 minutes ago   102MB
    [root@zyn01 zynubuntu]# docker run -it zynubuntu:1.0 /bin/bash
    
    root@4f8e45b45d89:/usr/local# ifconfig 
    eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
            inet 172.17.0.6  netmask 255.255.0.0  broadcast 172.17.255.255
            inet6 fe80::42:acff:fe11:6  prefixlen 64  scopeid 0x20<link>
            ether 02:42:ac:11:00:06  txqueuelen 0  (Ethernet)
            RX packets 7  bytes 586 (586.0 B)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 7  bytes 586 (586.0 B)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
    lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
            inet 127.0.0.1  netmask 255.0.0.0
            inet6 ::1  prefixlen 128  scopeid 0x10<host>
            loop  txqueuelen 1000  (Local Loopback)
            RX packets 0  bytes 0 (0.0 B)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 0  bytes 0 (0.0 B)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
    root@4f8e45b45d89:/usr/local# echo ${MYPATH}
    /usr/local

构建镜像注意事项

  1. 精简镜像用途:尽量让每个镜像的用途都比较集中单一, 避免构造大而复杂、多功能的镜像;
  2. 提供注释和维护者信息:Dockerfile也是一种代码,需要考虑方便后续的扩展和他人的使用;
  3. 正确使用版本号:使用明确的版本号信息,如1.0, 2.0, 而非依赖于默认的latest。通过版本号可以避免环境不一致导致的问题;
  4. 减少镜像层数:如果希望所生成镜像的层数尽量少, 则要尽量合并RUN、ADD 和COPY指令。通常情况下, 多个RUN指令可以合并为一条RUN指令;
  5. 使用.dockerignore文件:使用它可以标记在执行docker build时忽略的路径和文件,避免发送不必要的数据内容,从而加快整个镜像创建过程;
  6. 及时删除临时文件和缓存文件: 特别是在执行apt-get指令后,/var/cache/apt下面会缓存了一些安装包。
相关推荐
tangdou3690986553 小时前
Docker系列-5种方案超详细讲解docker数据存储持久化(volume,bind mounts,NFS等)
docker·容器
later_rql3 小时前
k8s-集群部署1
云原生·容器·kubernetes
漫无目的行走的月亮6 小时前
在Docker中运行微服务注册中心Eureka
docker
大G哥8 小时前
记一次K8S 环境应用nginx stable-alpine 解析内部域名失败排查思路
运维·nginx·云原生·容器·kubernetes
大道归简9 小时前
Docker 命令从入门到入门:从 Windows 到容器的完美类比
windows·docker·容器
zeruns80210 小时前
如何搭建自己的域名邮箱服务器?Poste.io邮箱服务器搭建教程,Linux+Docker搭建邮件服务器的教程
linux·运维·服务器·docker·网站
爱跑步的程序员~10 小时前
Docker
docker·容器
福大大架构师每日一题10 小时前
23.1 k8s监控中标签relabel的应用和原理
java·容器·kubernetes
程序那点事儿10 小时前
k8s 之动态创建pv失败(踩坑)
云原生·容器·kubernetes
疯狂的大狗10 小时前
docker进入正在运行的容器,exit后的比较
运维·docker·容器