您当前的位置:首页 > 电脑百科 > 程序开发 > 容器

24条Dockerfile及指令最佳实践

时间:2023-11-28 14:00:17  来源:微信公众号  作者:程序新视界

构建缓存

在镜像的构建过程中,Docker会根据Dockerfile指定的顺序执行每个指令。Dockerfile的每条指令都会将结果提交为新的镜像。然后,下一条指令基于上一条指令的镜像进行构建。

在执行每条指令之前,Docker都会在缓存中查找是否已经存在可重用的镜像,如果存在就使用现存的镜像,不再重复创建。

因此,为了有效地利用缓存,尽量保持Dockerfile一致,并且尽量在末尾修改:

FROM ubuntu

MAINTAINER author <somebody@company.com>

RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe"

RUN apt-get update

RUN apt-get upgrade -y

更改MAINTAINER指令会使Docker强制执行run指令来更新apt,而不是使用缓存。

如不希望使用缓存,在执行 docker build 时需加上参数--no-cache=true。

Docker中,构建缓存遵循的基本规则如下:

  1. 从缓存中存在的基础镜像(FROM指令指定)开始,下一条指令将和该基础镜像的所有子镜像进行匹配,检查这些子镜像被创建时使用的指令是否和被检查的指令完全一样。如果不是,则缓存失效。
  2. 多数情况中,使用其中一个子镜像来比较Dockerfile中的指令是足够的。然而,特定的指令需要做更多的判断。
  3. 对于ADD和COPY指令,镜像中对应文件的内容也会被检查,每个文件都会计算出一个校验值,通常是检查文件的校验和(checksum)。在缓存的查找过程中,会将这些校验和已存在镜像中的文件校验值进行对比。如果文件有任何改变,则缓存失效。
  4. 除了ADD和COPY指令,缓存匹配检查并不检查临时容器中的文件。例如,当使用RUN apt-get -y update命令更新了容器中的文件,并不会被缓存检查策略作为缓存匹配的依据。
  5. 一旦缓存失效,所有后续的Dockerfile指令都将产生新的镜像,缓存不会被使用。

使用多阶段构建

多阶段构建可以大幅度减小最终的镜像大小,而不需要去想办法减少中间层和文件的数量。因为镜像是在生成过程的最后阶段生成的,所以可以利用生成缓存来最小化镜像层。

例如,如果构建包含多个层,则可以将它们从变化频率较低(以确保生成缓存可重用)到变化频率较高的顺序排序:

  • 安装构建应用程序所需的依赖工具
  • 安装或更新依赖项
  • 构建你的应用

比如构建一个Go应用程序的Dockerfile可能类似于这样:

FROM golang:1.11-alpine AS build

# 安装项目需要的工具
# 运行 `docker build --no-cache .` 来更新依赖
RUN apk add --no-cache git
RUN go get Github.com/golang/dep/cmd/dep

# 通过 Gopkg.toml 和 Gopkg.lock 获取项目的依赖
# 仅在更新 Gopkg 文件时才重新构建这些层
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# 安装依赖库
RUN dep ensure -vendor-only

# 拷贝整个项目进行构建
# 当项目下面有文件变化的时候该层才会重新构建
COPY . /go/src/project/
RUN go build -o /bin/project

# 将打包后的二进制文件拷贝到 scratch 镜像下面,将镜像大小降到最低
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

使用标签

除非是在用Docker做实验,否则你应当通过 -t 选项来 docker build 新的镜像以便于标记构建的镜像。一个简单可读的标签可以帮助管理每个创建的镜像。

docker build -t="tuxknight/luckyPython/ target=_blank class=infotextkey>Python"

始终通过 -t 标记来构建镜像。

公开端口

Docker的核心概念是可重复和可移植,镜像应该可以运行在任何主机上并运行尽可能多的次数。在Dockerfile中可以映射私有和公有端口,但永远不要通过Dockerfile映射公有端口。这样运行多个镜像的情况下会出现端口冲突的问题。

EXPOSE 80:8080  # 80映射到host的8080,不提倡这种用法 
EXPOSE 80 # 80会被docker随机映射一个端口

EXPOSE指令用于声明容器将监听的端口。在EXPOSE指令中,端口号的格式为<容器端口>/<协议>。其中,容器端口是指在容器内部应用程序监听的端口,而协议是可选的,默认为TCP。

示例中,EXPOSE 80:8080表示容器将监听容器端口80,而宿主机可以使用端口8080来访问容器的80端口。也就是,容器的80端口映射到了宿主机的8080端口。

请注意,EXPOSE指令仅仅是声明容器将监听的端口,并不会自动进行端口映射。要实际进行端口映射,需要在运行容器时使用-p或-P选项。

CMD ENTRYPOINT语法

CMD和ENTRYPOINT支持两种语法:

CMD /bin/echo 

CMD ["/bin/echo"]

在第一种方式下,Docker会在命令前加上 /bin/sh -c,可能会导致一些意想不到的问题。在第二种方式下,CMD ENTRYPOINT是一个数组,执行的命令完全和期待的一样。

容器是短暂的

容器模型是进程而不是机器,不需要开机初始化。在需要时运行,不需要时停止,能够删除后重建,并且配置和启动的最小化。

.dockerignore 文件

在docker build的时候,对于一些不需要提交构建的文件用.dockerignore来进行忽略。忽略部分无用的文件和目录可以提高构建的速度。

不要在构建中升级版本

不在容器中更新,更新交给基础镜像来处理。

应用解耦

每个容器只运行一个进程,每个容器应用只关心一个方面的事情。将多个应用解耦到不同容器中,容器起到了隔离应用隔离数据的作用,可以更轻松地保证容器的横向扩展和复用。

例如一个Web应用程序可能包含三个独立的容器:Web应用、数据库、缓存,每个容器都是独立的镜像,分开运行。但这并不是说一个容器就只能跑一个进程,因为有的程序可能会自行产生其他进程,比如Celery就可以有很多个工作进程。

虽然每个容器跑一个进程是一条很好的法则,但这并不是一条硬性的规定。主要是希望一个容器只关注一件事情,尽量保持干净和模块化。如果容器互相依赖,你可以使用 Docker 容器网络 来把这些容器连接起来。

最小化镜像层数

在很早之前的版本中尽量减少镜像层数是非常重要的,不过现在的版本已经有了一定的改善了:

  • 只有RUN、COPY和ADD指令会创建层,其他指令会创建临时的中间镜像,但是不会直接增加构建的镜像大小了。
  • 多阶段构建的支持,允许我们把需要的数据直接复制到最终的镜像中,这就允许在中间阶段包含一些工具或者调试信息了,而且不会增加最终的镜像大小。

需要掌握好Dockerfile的可读性和文件系统层数之间的平衡。控制文件系统层数时会降低Dockerfile的可读性。而Dockerfile可读性高时,往往会导致更多的文件系统层数。

避免安装不必要的包

为了降低复杂性、减少依赖、减小文件大小和构建时间,应该避免安装额外的或者不必要的软件包。例如,不要在数据库镜像中包含一个文本编辑器。

使用特定标签

Dockerfile中FROM应始终包含依赖的基础镜像的完整仓库名和标签,如使用FROM debian:jessie而不是FROM debian。

多行参数排序

只要有可能,就将多行参数按字母顺序排序。这可以避免重复包含同一个包,更新包列表时也更容易,也更容易阅读和审查。建议在反斜杠符号  之前添加一个空格,可以增加可读性。

RUN apt-get update && apt-get install -y 
  bzr 
  cvs 
  git 
  mercurial 
  subversion

Dockerfile指令最佳实践

关于这些指令的使用建议可以帮助我们创建高效且可维护的Dockerfile。以下内容为Dockerfile指令部分的最佳实践。

FROM

尽可能使用当前的官方镜像作为基础镜像。推荐使用Debian镜像,大小保持在100MB上下,且仍是完整的发行版。

另外,根据情况也可考虑使用Alpine映像,因为它受到严格控制且较小(当前小于5MB),同时仍是完整的linux发行版。

LABEL标签

可以给镜像添加标签来帮助组织镜像、记录许可信息、辅助自动化构建等。每个标签一行,由LABEL开头加上一个或多个标签对。

下面的示例展示了各种不同的可能格式。#开头的行是注释内容。

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor="ACME Incorporated"
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

一个镜像可以包含多个标签,当然以上内容也可以写成下面这样,但是不是必须的:

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME Incorporated 
      com.example.is-production="" 
      com.example.version="0.0.1-beta" 
      com.example.release-date="2015-02-12"

PS:如果字符串包含空格,那么它必须被引用或者空格必须被转义。如果字符串包含内部引号字符("),则也可以将其转义。

RUN

为了保持Dockerfile文件的可读性以及可维护性,建议将过长的或复杂的RUN指令用反斜杠分割成多行,以提高可读性和可维护性。

RUN指令最常见的用法是安装包用的apt-get。因为RUN apt-get指令会安装包,所以有几个问题需要注意。

  • 避免运行apt-get upgrade或dist-upgrade,在无特权的容器中,很多必要的包不能正常升级。如果基础镜像过时了,应当联系维护者。如果你确定某个特定的包,比如foo,需要升级,使用apt-get install -y foo就行,该指令会自动升级foo包。
  • 永远将apt-get update和apt-get install一起执行,否则apt-get install会出现异常。
  • 推荐apt-get update && apt-get install -y package-a package-b这种方式,先更新,之后安装最新的软件包。
RUN apt-get update && apt-get install -y 
    aufs-tools 
    automake 
    btrfs-tools 
    build-essential 
    curl 
    dpkg-sig 
    git 
    iptables 
    libApparmor-dev 
    libcap-dev 
    libsqlite3-dev 
    lxc=1.0* 
    mercurial 
    parallel 
    reprepro 
    ruby1.9.1 
    ruby1.9.1-dev 
    s3cmd=1.1.0*

将apt-get update放在一条单独的RUN声明中会导致缓存问题以及后续的apt-get install失败。比如,假设有一个Dockerfile文件:

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl

构建镜像后,所有的层都在Docker的缓存中。假设后来又修改了其中的apt-get install添加了一个包:

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl Nginx

Docker发现修改后的RUN apt-get update指令和之前的完全一样。所以,apt-get update不会执行,而是使用之前的缓存镜像。因为apt-get update没有运行,后面的apt-get install可能安装的是过时的curl和nginx版本。

使用RUN apt-get update && apt-get install -y可以确保Dockerfiles每次安装的都是包的最新的版本,而且这个过程不需要进一步的编码或额外干预。这项技术叫做cache busting(缓存破坏)。

EXPOSE 指令

EXPOSE指令用于指定容器将要监听的端口。因此,要为应用程序使用常见的端口。

例如,提供Apache web服务的镜像应该使用EXPOSE 80,而提供MongoDB服务的镜像使用EXPOSE 27017。

对于外部访问,用户可以在执行docker run时使用一个-p参数来指示如何将指定的端口映射到所选择的端口。

ENV 指令

为了方便新程序运行,可以使用ENV指令来为容器中安装的程序更新PATH环境变量。例如使用ENV PATH /usr/local/nginx/bin:$PATH来确保CMD ["nginx"]能正确运行。

ENV指令也可用于为容器化的服务提供必要的环境变量,比如Postgres需要的PGDATA。最后,ENV也能用于设置常见的版本号,比如下面的示例:

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

类似于程序中的常量,这种方法可以只需改变ENV指令来自动的改变容器中的软件版本。

CMD

CMD指令是容器启动以后,默认的执行命令,需要重点理解下这个默认的含义,意思就是如果我们执行docker run没有指定任何的执行命令或者Dockerfile里面也没有指定ENTRYPOINT,那么就会使用CMD指定的执行命令执行了。这也说明了ENTRYPOINT才是容器启动以后真正要执行的命令。

所以经常遇到CMD会被覆盖的情况。为什么会被覆盖呢?主要还是因为CMD的定位就是默认,如果不额外指定,那么才会执行CMD命令,但是如果我们指定了的话那就不会执行CMD命令了,也就是说CMD会被覆盖。

CMD总共有三种用法:

CMD ["executable", "param1", "param2"]  # exec 形式
CMD ["param1", "param2"] # 作为 ENTRYPOINT 的默认参数
CMD command param1 param2  # shell 形式

CMD推荐使用CMD ["executable","param1","param2"]这样的格式。如果镜像是用来运行服务,需要使用CMD["apache2","-DFOREGROUND"],这种格式的指令适用于任何服务性质的镜像。

ENTRYPOINT 指令

根据官方定义来说ENTRYPOINT才是用于定义容器启动以后的执行程序的,允许将镜像当成命令本身来运行(用CMD提供默认选项),从名字也可以理解,是容器的入口。

ENTRYPOINT 一共有两种用法:

ENTRYPOINT ["executable", "param1", "param2"] (exec 形式)
ENTRYPOINT command param1 param2 (shell 形式)

对应命令行exec模式,也就是带中括号的,和CMD的中括号形式是一致的。但是这里貌似是在shell的环境下执行的,与cmd有区别。

如果run命令后面有执行命令,那么后面的全部都会作为ENTRYPOINT的参数。如果run后面没有额外的命令,但是定义了CMD,那么CMD的全部内容就会作为ENTRYPOINT的参数,这同时是上面我们提到的CMD的第二种用法。

所以说ENTRYPOINT不会被覆盖。当然如果要在run里面覆盖,也是有办法的,使用--entrypoint参数即可。

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

ADD COPY

虽然ADD与COPY功能类似,但推荐使用COPY。因为它比 ADD 更透明。COPY只支持基本的文件拷贝功能,更加的可控。而ADD具有更多特定,比如tar文件自动提取,支持URL。通常需要提取tarball中的文件到容器的时候才会用到ADD。

如果在Dockerfile中使用多个文件,每个文件应使用单独的COPY指令。这样,只有出现文件变化的指令才会不使用缓存。

为了控制镜像的大小,不建议使用ADD指令获取URL文件。正确的做法是在RUN指令中使用wget或curl来获取文件,并且在文件不需要的时候删除文件。

RUN mkdir -p /usr/src/things 
    && curl -SL http://example.com/big.tar.gz 
    | tar -xJC /usr/src/things 
    && make -C /usr/src/things all

VOLUME

VOLUME指令用于声明容器中的目录将被持久化保存,即在容器中创建的目录将被挂载到宿主机或其他容器中,以便数据可以在容器之间共享。

VOLUME指令应当暴露出数据库的存储位置,配置文件的存储以及容器中创建的文件或目录。由于容器结束后并不保存任何更改,应该把所有数据通过VOLUME保存到host中。

强烈建议使用VOLUME来管理镜像中的可变部分和用户可以改变的部分。

USER

如果服务不需要特权来运行,使用USER指令切换到非root用户。使用RUN groupadd -r MySQL && useradd -r -g mysql mysql之后用USER mysql切换用户。

要避免使用sudo来提升权限,因为它不可预期的TTY和信号转发行为可能造成的问题比它能解决的问题还多。如果你真的需要和sudo类似的功能(例如,以root权限初始化某个守护进程,以非root权限执行它),你可以使用gosu。我们可以去查看官方的一些镜像,很多都是使用的gosu。

最后,不要反复地切换用户,减少不必要的layers。

WORKDIR

为了清晰性和可靠性,WORKDIR的路径应该始终使用绝对路径。同时,使用WORKDIR来替代RUN cd ... && do-something这样难以维护的指令。后者难以阅读、排错和维护。



Tags:Docker   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Docker 和传统虚拟机有什么区别?
我有一个程序员朋友,他每年情人节都要送女朋友一台服务器。他说:“谁不想在过节当天收到一台 4核8g 的服务器呢?”“万一对方不要,我还能留着自己用。” 给他一次过节的机会,他能...【详细内容】
2024-03-26  Search: Docker  点击:(11)  评论:(0)  加入收藏
掌握Docker网络驱动程序:优化容器通信
Docker为在容器内包装、交付和运行应用程序提供了一个强大的平台,从而彻底改变了容器化。网络是容器化的重要组成部分,Docker提供了各种网络驱动程序来支持容器之间的通信以...【详细内容】
2024-03-22  Search: Docker  点击:(10)  评论:(0)  加入收藏
如何基于Docker镜像逆向生成Dockerfile
引言你是否曾经遇到过一个想要使用的 Docker 镜像,但却无法修改以适应你的特定需求?或者你可能发现了一个喜欢的 Docker 镜像,但想要了解它是如何构建的?在这两种情况下,将 Docke...【详细内容】
2024-03-07  Search: Docker  点击:(22)  评论:(0)  加入收藏
Docker与Docker Compose入门:释放你应用部署的威力
今天给大家介绍一项强大而有趣的技能,那就是使用 Docker 和 Docker Compose 来释放你的应用部署的威力!无论你是一名开发人员还是系统管理员,掌握这个技能都将为你的工作带来巨...【详细内容】
2024-01-17  Search: Docker  点击:(65)  评论:(0)  加入收藏
Docker镜像与容器的交互及在容器内部执行代码的原理与实践
Docker作为一种流行的容器技术,已经成为现代应用程序开发和部署的重要工具。在Docker中,镜像是构建和运行容器的基础,而容器则是基于镜像创建的可执行实例。Docker镜像与容器的...【详细内容】
2024-01-10  Search: Docker  点击:(75)  评论:(0)  加入收藏
如何在 Ubuntu 上安装 Docker
使用 Docker 意味着开启一个新的计算领域,但如果你刚刚开始使用 Docker,安装可能看起来是一项艰巨的任务。在 Ubuntu 上安装 Docker 有两种推荐的方法: 从 Ubuntu 的仓库安装 D...【详细内容】
2024-01-04  Search: Docker  点击:(124)  评论:(0)  加入收藏
七个杀手级Docker命令
Docker是一个容器化平台,通过操作系统级别的虚拟化技术,实现软件的打包和容器化运行。借助Docker,开发人员能够将应用程序以容器的形式进行部署,但在此之前需要构建Docker镜像。...【详细内容】
2023-12-22  Search: Docker  点击:(140)  评论:(0)  加入收藏
Docker容器如何打包应用程序的代码和依赖项?
Docker容器通过将应用程序的代码和所有依赖项打包到一个独立的软件包中,实现了应用程序的快速部署和移植。下面是Docker容器实现这一目标的步骤:打包应用程序:使用Docker工具将...【详细内容】
2023-12-20  Search: Docker  点击:(130)  评论:(0)  加入收藏
DBA视角:把数据库放入Docker是一个馊主意
对于无状态的应用服务而言,容器是一个相当完美的开发运维解决方案。然而对于带持久状态的服务 &mdash;&mdash; 数据库来说,事情就没有那么简单了。生产环境的数据库是否应当放...【详细内容】
2023-12-18  Search: Docker  点击:(197)  评论:(0)  加入收藏
Docker容器编排技术解析
一、容器编排介绍容器编排是现代云原生应用管理的核心,它涉及在大规模的环境中自动化部署、管理、扩展和网络配置容器。随着微服务架构的兴起和应用的复杂性增加,容器编排成为...【详细内容】
2023-12-15  Search: Docker  点击:(244)  评论:(0)  加入收藏
▌简易百科推荐
Docker 和传统虚拟机有什么区别?
我有一个程序员朋友,他每年情人节都要送女朋友一台服务器。他说:“谁不想在过节当天收到一台 4核8g 的服务器呢?”“万一对方不要,我还能留着自己用。” 给他一次过节的机会,他能...【详细内容】
2024-03-26  小白debug  微信公众号  Tags:Docker   点击:(11)  评论:(0)  加入收藏
掌握Docker网络驱动程序:优化容器通信
Docker为在容器内包装、交付和运行应用程序提供了一个强大的平台,从而彻底改变了容器化。网络是容器化的重要组成部分,Docker提供了各种网络驱动程序来支持容器之间的通信以...【详细内容】
2024-03-22    51CTO  Tags:Docker   点击:(10)  评论:(0)  加入收藏
Containerd容器管理
Nginx 指定容器名称 使用 ctr container create 命令创建容器后,容器并没有处于运行状态,其只是一个静态的容器。容器基本操作容器基本操作主要是 ctr image 命令,查看命令帮...【详细内容】
2024-03-20  云原生运维圈  微信公众号  Tags:容器   点击:(13)  评论:(0)  加入收藏
如何基于Docker镜像逆向生成Dockerfile
引言你是否曾经遇到过一个想要使用的 Docker 镜像,但却无法修改以适应你的特定需求?或者你可能发现了一个喜欢的 Docker 镜像,但想要了解它是如何构建的?在这两种情况下,将 Docke...【详细内容】
2024-03-07  云原生运维圈  微信公众号  Tags:Docker   点击:(22)  评论:(0)  加入收藏
Kubernetes是什么?主要特点是什么?
Kubernetes是什么?Kubernetes,也称为K8s,是一个开源的容器编排系统,由Google首次开发和维护。它允许容器化的应用程序在集群中自动部署、扩展和管理。Kubernetes提供了一种容器...【详细内容】
2024-02-01    简易百科  Tags:Kubernetes   点击:(153)  评论:(0)  加入收藏
我们一起聊聊容器资源自愈
在企业实际在使用容器这类资源的时候,除了技术本身,要考虑的其他问题也会很多。企业管理的容器有千千万万,出于效率考虑,对于有特殊需求的容器如何进行批量创建和管理呢,这就需要...【详细内容】
2024-01-30  匠心独运维妙维效  微信公众号  Tags:容器   点击:(47)  评论:(0)  加入收藏
Docker与Docker Compose入门:释放你应用部署的威力
今天给大家介绍一项强大而有趣的技能,那就是使用 Docker 和 Docker Compose 来释放你的应用部署的威力!无论你是一名开发人员还是系统管理员,掌握这个技能都将为你的工作带来巨...【详细内容】
2024-01-17  waynblog  微信公众号  Tags:Docker   点击:(65)  评论:(0)  加入收藏
Docker镜像与容器的交互及在容器内部执行代码的原理与实践
Docker作为一种流行的容器技术,已经成为现代应用程序开发和部署的重要工具。在Docker中,镜像是构建和运行容器的基础,而容器则是基于镜像创建的可执行实例。Docker镜像与容器的...【详细内容】
2024-01-10  编程技术汇  今日头条  Tags:Docker   点击:(75)  评论:(0)  加入收藏
如何在 Ubuntu 上安装 Docker
使用 Docker 意味着开启一个新的计算领域,但如果你刚刚开始使用 Docker,安装可能看起来是一项艰巨的任务。在 Ubuntu 上安装 Docker 有两种推荐的方法: 从 Ubuntu 的仓库安装 D...【详细内容】
2024-01-04    Linux中国  Tags:Docker   点击:(124)  评论:(0)  加入收藏
从Kubernetes的探针到DevOps
今天在群里又看有人问如何设置 Kubernetes 的探针,感觉要补充的话太多了,结合我们在一些 DevOps 项目中痛苦的体验,今天一劳永逸的全部说完,此外,也为大家展现一下为什么 DevOps...【详细内容】
2023-12-27  云云众生s  微信公众号  Tags:Kubernetes   点击:(113)  评论:(0)  加入收藏
站内最新
站内热门
站内头条