Dockerfile完全指南

本文涉及的产品
Elasticsearch Serverless通用抵扣包,测试体验金 200元
简介: Dockerfile完全指南

一、前言

Dockerfile是一个文本文档,它包含用户可以在命令行上调用的所有指令,每一条指令构建一层镜像。在日常开发中我们常常需要自己编写Dockerfile来构建镜像,而构建一个精巧、实用且高品质的镜像对运行环境来说尤为重要。下面我们来排一排如何构建这样的镜像。

二、我们的目标

构建一个精巧、实用且高品质的镜像需要达成的目标:

  • 更快的构建镜像速度
  • 小的镜像大小
  • 少的镜像层
  • 理使用镜像缓存
  • 加Dockerfile可读性
  • 让Docker容器使用起来更简单

三、构建技巧

3.1 基础镜像的选择 (FROM)

任何一个docker镜像都会依赖一个基础镜像,也就是Dockerfile的第一个FROM命令,这是docker镜像的基础。这个基础镜像的选择,在很大程度上会决定一个docker镜像的起点,所以选择一个好的基础镜像还是十分重要的。通常我们选择基础镜像的时候,会遵循以下几点:

  • 官方镜像优于非官方的镜像,如果没有官方镜像,则尽量选Dockerfile开源的
  • 要选择固定版本tag而不是每次都使用latest,选择最新的版本有风险
  • 尽量选择体积小且适合当前需求的镜像(同一个版本,不同的TAG,其镜像大小也不一样)

3.2 镜像的大小和分层

每一行的RUN命令都会产生一层image layer, 若是Dockerfile存在很多RUN,就会导致镜像的臃肿。如下面的Dockerfile有4个RUN,就会有4层。

FROM xiaohezi/centos7:jdk1.8
ENV VERSION=6.5.4
MAINTAINER xiaohezi
COPY ./elasticsearch-${VERSION}.tar.gz /usr/local
RUN cd /usr/local && tar zxvf elasticsearch-${VERSION}.tar.gz && rm -f elasticsearch-${VERSION}.tar.gz
RUN mkdir -p  /data/elasticsearch/data && mkdir /data/elasticsearch/log
ADD ./elasticsearch.yml /usr/local/elasticsearch-${VERSION}/config/elasticsearch.yml
RUN useradd es
RUN chown -R es:es /usr/local/elasticsearch-${VERSION}/ && chown -R es:es /data
USER es
#WORKDIR指令用于指定容器的一个目录, 容器启动时执行的命令会在该目录下执行
WORKDIR /usr/local/elasticsearch-${VERSION}
#目录就会在运行时自动挂载为匿名卷,任何向/data/elasticsearch/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化
VOLUME ["/data/elasticsearch/data","/data/elasticsearch/log"]
#CMD用于设置默认执行的命令
CMD ["/usr/local/elasticsearch-6.5.4/bin/elasticsearch"]
EXPOSE 9200 9300

有一首歌不知道大家听过没,“如果你愿意一层一层剥开我的心 你会鼻酸 你会流泪”,这是歌曲《洋葱》中的歌词,镜像就好比是洋葱,都有很多层,每一条 Dockerfile 指令都会提交为一个镜像层,下一条指令都是基于上一条指令构建的。而将RUN指令合并的话,有助于减少docker的层级。但是并不是所有的RUN指令都需要合并在一起,通常情况下我们把变化频率相同的指令合并在一起,变化频率不一样的还是分开放,因为如果频率不一样,会造成很多无用的变更和构建,得不偿失。

如下改进后的Dockerfile只有一个RUN:

FROM pkulaw/centos7:jdk1.8
ENV VERSION=6.5.4
MAINTAINER xiaohezi
COPY ./elasticsearch-${VERSION}.tar.gz /usr/local
RUN cd /usr/local && tar zxvf elasticsearch-${VERSION}.tar.gz && \
    rm -f elasticsearch-${VERSION}.tar.gz && \
    mkdir -p  /data/elasticsearch/data && \
    mkdir -p /data/elasticsearch/log && \
    useradd es && \
    chown -R es:es /usr/local/elasticsearch-${VERSION}/ && \
    chown -R es:es /data 
ADD ./elasticsearch.yml /usr/local/elasticsearch-${VERSION}/config/elasticsearch.yml
USER es
#WORKDIR指令用于指定容器的一个目录, 容器启动时执行的命令会在该目录下执行
WORKDIR /usr/local/elasticsearch-${VERSION}
#目录就会在运行时自动挂载为匿名卷,任何向/data/elasticsearch/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化
VOLUME ["/data/elasticsearch/data","/data/elasticsearch/log"]
#CMD用于设置默认执行的命令
CMD ["/usr/local/elasticsearch-6.5.4/bin/elasticsearch"]
EXPOSE 9200 9300

3.3 文件复制和目录操作 (ADD,COPY)

COPY 和 ADD都可以把文件复制到镜像中,但是两个命令的效果却不一样。COPY 和 ADD 都可以把local的文件复制到镜像里,如果目标目录不存在,还会自动创建。ADD 比 COPY高级一点的地方就是,如果复制的是一个压缩文件时,ADD会自动去解压缩文件。若复制的是一个普通文件用COPY即可,若是需要自动解压缩就用ADD。

3.4 构建参数和环境变量 (ARG vs ENV)

ARG 和 ENV 是经常容易被混淆的两个Dockerfile的语法,都可以用来设置一个“变量”。但实际上两者有很多的不同。

1)ENV

如下实例:

FROM pkulaw/centos7:jdk1.8
ENV VERSION=6.5.4
MAINTAINER xiaohezi
COPY ./elasticsearch-${VERSION}.tar.gz /usr/local
RUN cd /usr/local && tar zxvf elasticsearch-${VERSION}.tar.gz && \
    rm -f elasticsearch-${VERSION}.tar.gz && \
    mkdir -p  /data/elasticsearch/data && \
    mkdir -p /data/elasticsearch/log && \
    useradd es && \
    chown -R es:es /usr/local/elasticsearch-${VERSION}/ && \
    chown -R es:es /data 
ADD ./elasticsearch.yml /usr/local/elasticsearch-${VERSION}/config/elasticsearch.yml
USER es
#WORKDIR指令用于指定容器的一个目录, 容器启动时执行的命令会在该目录下执行
WORKDIR /usr/local/elasticsearch-${VERSION}
#目录就会在运行时自动挂载为匿名卷,任何向/data/elasticsearch/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化
VOLUME ["/data/elasticsearch/data","/data/elasticsearch/log"]
#CMD用于设置默认执行的命令
CMD ["/usr/local/elasticsearch-6.5.4/bin/elasticsearch"]
EXPOSE 9200 9300

通过ENV定义的环境变量,会永久的保存在该镜像创建的任何容器中。

2)ARG

如下实例:

FROM pkulaw/centos7:jdk1.8
ARG VERSION=6.5.4
MAINTAINER xiaohezi
COPY ./elasticsearch-${VERSION}.tar.gz /usr/local
RUN cd /usr/local && tar zxvf elasticsearch-${VERSION}.tar.gz && \
    rm -f elasticsearch-${VERSION}.tar.gz && \
    mkdir -p  /data/elasticsearch/data && \
    mkdir -p /data/elasticsearch/log && \
    useradd es && \
    chown -R es:es /usr/local/elasticsearch-${VERSION}/ && \
    chown -R es:es /data 
ADD ./elasticsearch.yml /usr/local/elasticsearch-${VERSION}/config/elasticsearch.yml
USER es
#WORKDIR指令用于指定容器的一个目录, 容器启动时执行的命令会在该目录下执行
WORKDIR /usr/local/elasticsearch-${VERSION}
#目录就会在运行时自动挂载为匿名卷,任何向/data/elasticsearch/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化
VOLUME ["/data/elasticsearch/data","/data/elasticsearch/log"]
#CMD用于设置默认执行的命令
CMD ["/usr/local/elasticsearch-6.5.4/bin/elasticsearch"]
EXPOSE 9200 9300

ARG 定义的变量只会存在于镜像构建过程,启动容器后并不保留这些变量。ENV和ARG区别是:ARG 可以在镜像build的时候通过添加 --build-arg来动态修改value。

3.5 CMD 和ENTRYPOINT命令

1、CMD命令

官网CMD命令介绍

在官网中有这么一句话,原文如下:

The main purpose of a CMD is to provide defaults for an executing container. These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINT instruction as well.

翻译如下:cmd给出的是一个容器的默认的可执行体。也就是容器启动以后,默认的执行的命令。这些默认值可以包含可执行文件,也可以省略可执行文件,在这种情况下,您还必须指定一条ENTRYPOINT 指令。

重点就是这个“默认”。这意味着,如果docker run既没有指定任何的执行命令且dockerfile里面也没有entrypoint命令,这个时候,就会使用这个默认的CMD指定的默认的执行命令执行。从这里我们可以看出CMD的角色定位是默认的容器启动执行命令。而entrypoint,它才是真正的容器启动以后要执行命令。还有个说法是“CMD会被覆盖”,其实为什么会覆盖?因为CMD的角色定位就是默认,如果你不额外指定,那么就执行CMD的命令,否则呢?只要你指定了,那么就不会执行CMD,也就是CMD会被覆盖。

1)CMD的三种用法

CMD指令有三种形式:
CMD ["executable","param1","param2"] (执行形式,这是首选形式)
CMD ["param1","param2"] (作为ENTRYPOINT命令的默认参数)
CMD command param1 param2 (shell形式)
  • 用法1带有中括号的形式,表示exec形式,官网首推这种形式。这时,命令没有再任何shell终端环境下,如果我们要执行shell,必须把shell加入到中括号的参数中。这种用法就像一个c语言的exec函数,意思是我们要执行一个进程。如果采用非shell的方法,就如下实例:
FROM nginx:stable-alpine
COPY default.conf /etc/nginx/conf.d/default.conf
COPY dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

注:如果采用中括号形式,那么第一个参数必须是命令的全路径才行。而且,一个dockerfile至多只能有一个cmd,如果有多个,只有最后一个生效。

  • 用法2:就是执行命令带参数,就是只有参数,而这些参数将作为ENTRYPOINT命令的默认参数,当以第二种方式使用的时候,Dockerfile文件中必须包含一条ENTRYPOINT命令,两者联合使用。
  • 法3:shell form,即没有中括号的形式。那么命令command默认是在“/bin/sh -c”下执行的。比如下面的Dockerfile:
FROM centos
CMD echo "hello xiaohezi!"

2)CMD总结

  • CMD可以用来设置容器启动时默认会执行的命令
  • 如果docker container run启动容器时指定了其它命令,则CMD命令会被忽略
  • 如果定义了多个CMD,只有最后一个会被执行。

2、ENTRYPOINT命令

官网ENTRYPOINT介绍

在官网中有这么一句话:

An ENTRYPOINT allows you to configure a container that will run as an executable.

翻译如下:entrypoint才是正统地用于定义容器启动以后的执行体的,其实我们从名字也可以理解,这个是容器的“入口”。

1)ENTRYPOINT的两种用法

ENTRYPOINT 有两种形式:
ENTRYPOINT ["executable", "param1", "param2"](执行形式,首选)
ENTRYPOINT command param1 param2(shell形式)
  • 用法1:命令行模式(官网推荐模式)也就是带中括号的。即使在docker run中有指定命令的情况下,它依然会被执行,一般情况下不会被覆盖,除非我们在docker run命令中指定--entrypoint参数,这个命令才会被覆盖。而且run命令后面的东西,全部都会作为entrypoint的参数,如果run后面没有额外的东西,但是有cmd,那么cmd的全部内容会作为entrypoint的参数(这种情况就是ENTRYPOINT 和 CMD 联合使用,也是CMD的用法2)。切记每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。

如下实例:

FROM openjdk:8u292-oraclelinux8
EXPOSE 9999
ADD test.jar /deployments/test.jar
RUN bash -c 'touch /deployments/test.jar'
ENTRYPOINT ["java","-jar","/deployments/test.jar","--spring.config.location=/deployments/config/application-prod.yml"]
  • 法2:shell模式,在这种模式下,任何RUN和CMD的参数都无法被传入到ENTRYPOINT里。

2)ENTRYPOINT总结

ENTRYPOINT 也可以设置容器启动时要执行的命令,但是和CMD是有区别的。CMD 设置的命令,可以在docker container run 时传入其它命令,覆盖掉 CMD 的命令,但是 ENTRYPOINT 所设置的命令是一定会被执行的。ENTRYPOINT 和 CMD 可以联合使用,ENTRYPOINT 设置执行的命令,CMD传递参数

3、综合推荐用法

一般还是会用entrypoint的中括号形式作为docker 容器启动以后的默认执行命令,里面放的是不变的部分,可变部分比如命令参数可以使用cmd的形式提供默认版本,也就是run里面没有任何参数时使用的默认参数。如果我们想用默认参数,就直接run,否则想用其他参数,就run里面加参数。

3.6 合理使用缓存

Docker 构建过程中,每一条 Dockerfile 指令都会提交为一个镜像层,下一条指令都是基于上一条指令构建的。如果构建时发现要构建的镜像层的父镜像层已经存在,并且下一条命令使用了相同的指令,即可命中构建缓存。因此,基于 Docker 构建时的缓存特性,我们可以把不轻易改变的指令放到 Dockerfile 前面(例如安装软件包),而可能经常发生改变的指令放在 Dockerfile 末尾(例如编译应用程序)。这样前面的一些命令就可以命中构建缓存,即使用CACHED,提高构建速度。

3.7 合理使用 .dockerignore

什么是Docker build context?

Docker是client-server架构,理论上Client和Server可以不在一台机器上。在构建docker镜像的时候,需要把所需要的文件由CLI(client)发给Server,这些文件实际上就是build context。构建的时候,第一行输出就是发送build context。. 这个点参数就是代表了build context所指向的目录。有了.dockerignore文件后,我们可以把一些不需要的文件或目录给忽略掉。

*/temp*
*/*/temp*
.git
.idea
logs
*.iml
!test.iml

注:上面代码表示,这几个路径要排除,不要打包进入 image 文件。当然了若是没有要排除的,此文件可以不新建。

参数解析:

*/temp*        #排除根目录的任何直接子目录中以temp开头的文件和目录。例如,普通文件/somedir/temporary.txt被排除在外,目录/somedir/temp也是如此。
*/*/temp*      #将以temp开头的文件和目录从比根级低两级的任何子目录中排除。例如,/somedir/subdir/temporary.txt被排除在外。
temp?          #排除根目录中名称为一个字符扩展名的文件和目录temp。例如,/tempa和/tempb被排除在外。
.git           #排除.git文件
.idea          #排除.idea文件
logs           #排除logs文件
*.iml          #排除以.iml文件,不包含test.iml文件
!test.iml      #排除不包含test.iml文件

如需更多用法,请查看官网:

https://docs.docker.com/engine/reference/builder/#dockerignore-file


四、编写Dockerfile


在日常开中中我们编写最多的就是应用服务的Dockerfile文件了,如下实例:

FROM openjdk:8u292-oraclelinux8
EXPOSE 8702
#拷贝字体文件
COPY sim /usr/share/fonts/
#设置字符集
ENV LANG en_US.UTF-8
#创建字体文件的缓存信息,完成字体配置
RUN fc-cache
ADD search.jar /deployments/search.jar
RUN bash -c 'touch /deployments/search.jar'
ENTRYPOINT ["java","-jar","/deployments/search.jar","--spring.config.location=/deployments/config/application-prod.yml"]

构建:

docker build -f Dockerfile -t search:1.0.0 .

编写docker-compose-search.yml文件:

version: '3.7'
services:
  search:
    image: search:1.0.0
    container_name: search
    restart: always
    environment:
      TZ: Asia/Shanghai
    volumes:
      - /data/back-end/case/search/config:/deployments/config
      - /data/back-end/case/search/logs:/logs
    ports:
      - "8702:8702"
    networks:
      - pk_net
networks:
  pk_net:
    external: true

创建docker自定义网络:

sudo docker network create --driver bridge --subnet 10.139.0.0/16 --gateway 10.139.0.1 pk_net
##预先创建一个自定义的网络pkulaw_net,此处的10.139可以自定义,不冲突即可

创建容器并启动:

docker-compose -p search -f docker-compose-search.yml up -d


相关实践学习
以电商场景为例搭建AI语义搜索应用
本实验旨在通过阿里云Elasticsearch结合阿里云搜索开发工作台AI模型服务,构建一个高效、精准的语义搜索系统,模拟电商场景,深入理解AI搜索技术原理并掌握其实现过程。
ElasticSearch 最新快速入门教程
本课程由千锋教育提供。全文搜索的需求非常大。而开源的解决办法Elasricsearch(Elastic)就是一个非常好的工具。目前是全文搜索引擎的首选。本系列教程由浅入深讲解了在CentOS7系统下如何搭建ElasticSearch,如何使用Kibana实现各种方式的搜索并详细分析了搜索的原理,最后讲解了在Java应用中如何集成ElasticSearch并实现搜索。  
相关文章
|
Kubernetes 搜索推荐 数据安全/隐私保护
Containerd ctr、crictl、nerdctl 实战
Containerd ctr、crictl、nerdctl 实战
4288 1
|
8月前
|
机器学习/深度学习 数据采集 自然语言处理
HuggingFace Transformers 库深度应用指南
本文首先介绍HuggingFace Tra环境配置与依赖安装,确保读者具备Python编程、机器学习和深度学习基础知识。接着深入探讨Transformers的核心组件,并通过实战案例展示其应用。随后讲解模型加载优化、批处理优化等实用技巧。在核心API部分,详细解析Tokenizers、Models、Configuration和Dataset的使用方法。文本生成章节则涵盖基础概念、GPT2生成示例及高级生成技术。最后,针对模型训练与优化,介绍预训练模型微调、超参数优化和推理加速等内容。通过这些内容,帮助读者掌握HuggingFace Transformers的深度使用,开发高效智能的NLP应用。
1174 22
|
应用服务中间件 nginx Docker
深入理解Dockerfile:构建镜像的详细解释与常用命令(上)
Docker 是一种流行的容器化平台,可将应用程序和其依赖项打包到一个独立的、可移植的容器中。Dockerfile 是构建 Docker 镜像的文本文件,它包含了一系列的指令和配置,用于定义镜像的构建过程。本文将深入解释 Dockerfile 的工作原理,并介绍常用的 Dockerfile 指令和构建命令,以帮助读者更好地理解和使用 Docker。
2220 0
|
Java Spring
No qualifying bean of type 'XXXXX' available
该文档描述了Spring框架中`org.springframework.beans.factory.NoSuchBeanDefinitionException`异常的处理方法,该异常是因为无法找到类型为`com.weblog.auth.mapper.UserMapper`的bean导致。解决办法包括在对应的Mapper上添加`@Mapper`注解,并在启动类上添加`@MapperScan`注解以扫描包含Mapper接口的文件夹。
837 8
|
负载均衡 算法 应用服务中间件
Docker Swarm总结+service创建和部署、overlay网络以及Raft算法(2/5)
Docker Swarm总结+service创建和部署、overlay网络以及Raft算法(2/5)
1233 0
|
前端开发 Go API
Gin vs Beego: Golang的Web框架之争
Gin vs Beego: Golang的Web框架之争
|
Oracle Java 关系型数据库
|
Docker 容器
docker 设置国内镜像源
docker 设置国内镜像源
85015 1
|
存储 Linux 应用服务中间件
Docker Volume 看这一篇就够了
Docker Volume 看这一篇就够了
15226 3
Docker Volume 看这一篇就够了
|
缓存 网络协议 Linux
Dockerfile构建镜像过程中的错误记录及解决方法
本文记录了在一次使用Dockefile构建镜像的途中遇到的问题,以及后续的解决方法。
10552 1