文章目录
Dockerfile简介
Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的配置文件脚本。Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程等等。
创建镜像的方式以及镜像和容器的关系如下图所示:
构建三步骤:
- 编写Dockerfile文件
- docker build 命令构建镜像
- docker run以镜像运行容器实例
基本结构
基础知识:
-
每条保留字指令都必须为大写字母 且后面要跟随至少一个参数;
-
指令按照从上到下,顺序执行;
-
#表示注释
-
每条指令都会创建一个新的镜像层并对镜像进行提交
Dockerfile主体内容分为四部分:
- 基础镜像信息
- 维护者信息
- 镜像操作指令
- 容器启动指令
Docker执行Dockerfile的流程:
- docker从基础镜像运行容器
- 执行一条指令并对容器进行修改
- 执行类似docker commit的操作提交一个新的镜像层
- docker再基于刚提交的镜像运行一个新容器
- 执行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环境。
两者具体对比引用博客
每条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中,
ADD
和COPY
都是用于将文件从构建上下文或URL复制到镜像中的指令。虽然这两个指令有相似的功能,但它们在使用上有一些关键的区别:
基本功能:
COPY
:基本上用于复制本地文件或目录到镜像中的指定路径,它只关注从构建上下文到镜像的简单复制。ADD
:除了具有COPY的功能外,还支持两个附加功能:自动解压缩压缩文件(如tar压缩包)到镜像中,以及可以从URL直接下载文件到镜像中。使用场景:
COPY
应该是复制本地文件到Docker镜像时的首选指令,因为它的行为非常直接和清晰。ADD
更适用于需要自动解压缩文件或从远程URL下载文件的场景。例如,如果你想在构建过程中将一个tar文件的内容自动解压到镜像中,可以使用ADD
。建议:
- Docker官方推荐尽可能使用
COPY
,因为它更透明。ADD
的额外功能(如解压缩和支持URL)可能会引入意外的复杂性,比如不明确的来源和自动解压行为可能不是你想要的。示例:
使用COPY:
dockerfileCOPY ./localfolder /destinationfolder
使用ADD:
dockerfileADD 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形式,这会带来几个问题:
- 阻止使用CMD命令行参数 :当你使用ENTRYPOINT的shell形式时,它会忽略任何CMD提供的命令行参数。这是因为在shell形式下,ENTRYPOINT指令指定的命令会被直接传递给
/bin/sh -c
来执行,而不会与CMD指令中的参数结合。- 不作为容器的PID 1运行 :通常在Docker容器中,第一个启动的进程(PID 1)负责处理系统信号(例如SIGTERM和SIGKILL等)。但是,如果你使用ENTRYPOINT的shell形式,实际上
/bin/sh -c
成为了PID 1。这意味着你的应用程序实际上是作为/bin/sh -c
的子进程启动的,而不是直接作为PID 1。- 不接收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的镜像。
-
准备jdk包上传至节点
bashwget https://mirrors.yangxingzhen.com/jdk/jdk-8u181-linux-x64.tar.gz
-
准备Dockerfile文件
dockerfileFROM 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
-
构建镜像
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
-
运行镜像
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命令
-
编写Dockerfile文件
dockerfileFROM 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
-
构建镜像
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
-
运行容器
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
构建镜像注意事项
- 精简镜像用途:尽量让每个镜像的用途都比较集中单一, 避免构造大而复杂、多功能的镜像;
- 提供注释和维护者信息:Dockerfile也是一种代码,需要考虑方便后续的扩展和他人的使用;
- 正确使用版本号:使用明确的版本号信息,如1.0, 2.0, 而非依赖于默认的latest。通过版本号可以避免环境不一致导致的问题;
- 减少镜像层数:如果希望所生成镜像的层数尽量少, 则要尽量合并RUN、ADD 和COPY指令。通常情况下, 多个RUN指令可以合并为一条RUN指令;
- 使用.dockerignore文件:使用它可以标记在执行docker build时忽略的路径和文件,避免发送不必要的数据内容,从而加快整个镜像创建过程;
- 及时删除临时文件和缓存文件: 特别是在执行apt-get指令后,/var/cache/apt下面会缓存了一些安装包。