0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Dockerfile的最佳实践

汽车电子技术 来源:程序猿技术大咖 作者: xcbey0nd 2023-01-20 10:59 次阅读

微信截图_20230105161930.png

随着应用的容器化、上云后,将伴随着 Docker 镜像的构建,构建 Docker 镜像成为了最基本的一步,其中 Dockerfile 便是用来构建镜像的一种文本文件,镜像的优劣全靠 Dockerfile 编写的是否合理、合规。本文将讲述编写 Dockerfile 的一些最佳实践和技巧,让我们的镜像更小、更优。

1、Docker 镜像是如何工作的

首先,我们一起回顾下 Docker 镜像的相关概念及工作流程吧。

1.1 镜像

镜像(image)是一堆只读层(read-only layer)的统一视角,也许这个定义有些难以理解,下面的这张图能够帮助您理解镜像的定义。

微信截图_20230105161930.png

从左边我们看到了多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是 Docker 内部的实现细节,并且能够在主机的文件系统上访问到。统一文件系统技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。我们可以在图片的右边看到这个视角的形式。

您可以在您的主机文件系统上找到有关这些层的文件。需要注意的是,在一个运行中的容器内部,这些层是不可见的。在我的主机上,我发现它们存在于 /var/lib/docker/overlay2 目录下。

1.2 镜像分层结构

为什么说是镜像分层结构,因为 Docker 镜像是以层来组织的,可以通过命令 docker image inspect 或者 docker inspect 来查看镜像包含哪些层。

例如,镜像 busybox :

xcbeyond@xcbeyonddeMacBook-Pro ~ % docker inspect busybox
[
    {
        "Id": "sha256:3c277069c6ae3f3572998e727b973ff7418c3962b9403de4b3a3f8624399b8fa",
        "RepoTags": [
            "busybox:latest"
        ],
        "RepoDigests": [
            "busybox@sha256:d2b53584f580310186df7a2055ce3ff83cc0df6caacf1e3489bff8cf5d0af5d8"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2022-04-14T00:39:25.923517152Z",
        "Container": "39aaf4eecc48824531078c316f5b16e97549417e07c8f90b26ae16053111ea57",
        "ContainerConfig": {
            "Hostname": "39aaf4eecc48",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"sh\"]"
            ],
            "Image": "sha256:3289bc85dc0eba79657979661460c7f6f97688ad8a4f93174e0cabdd6b09a365",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "20.10.12",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "sh"
            ],
            "Image": "sha256:3289bc85dc0eba79657979661460c7f6f97688ad8a4f93174e0cabdd6b09a365",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "arm64",
        "Variant": "v8",
        "Os": "linux",
        "Size": 1411540,
        "VirtualSize": 1411540,
        "GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/e89181e7cadd3a6ee49f66bae34fed369621a1a5cfbe0003ce4621d0eec020e6/merged",
                "UpperDir": "/var/lib/docker/overlay2/e89181e7cadd3a6ee49f66bae34fed369621a1a5cfbe0003ce4621d0eec020e6/diff",
                "WorkDir": "/var/lib/docker/overlay2/e89181e7cadd3a6ee49f66bae34fed369621a1a5cfbe0003ce4621d0eec020e6/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:31a5597e16d3c5adaaf5826162216e256126d2fbf1beaa2b6c45c1822a2b9ca3"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

其中,RootFS 就是镜像 busybox:latest 的镜像层,只有一层,这层数据是存储在宿主机哪里的呢?动手实践的同学会在上面的输出中看到一个叫做 GraphDriver 的字段内容如下:

"GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/e89181e7cadd3a6ee49f66bae34fed369621a1a5cfbe0003ce4621d0eec020e6/merged",
                "UpperDir": "/var/lib/docker/overlay2/e89181e7cadd3a6ee49f66bae34fed369621a1a5cfbe0003ce4621d0eec020e6/diff",
                "WorkDir": "/var/lib/docker/overlay2/e89181e7cadd3a6ee49f66bae34fed369621a1a5cfbe0003ce4621d0eec020e6/work"
            },
            "Name": "overlay2"
        }

GraphDriver 负责镜像本地的管理和存储以及运行中的容器生成镜像等工作,可以将 GraphDriver 理解成镜像管理引擎,我们这里的例子对应的引擎名字是 overlay2(overlay 的优化版本)。除了 overlay 之外,Docker 的 GraphDriver 还支持 btrfs、aufs、devicemapper、vfs 等。

我们可以看到其中的 Data 包含了多个部分,这个对应 OverlayFS 的镜像组织形式,虽然我们上面的例子中的 busybox 镜像只有一层,但是正常情况下很多镜像都是由多层组成的。

1.3 Dockerfile、镜像、容器间的关系

Dockerfile 是软件的原材料,Docker 镜像是软件的交付品,而 Docker 容器则可以认为是软件的运行态。从应用软件的角度来看,Dockerfile、Docker 镜像与 Docker 容器分别代表软件的三个不同阶段,Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器则涉及部署与运维,三者缺一不可,合力充当 Docker 体系的基石。

简单来讲,Dockerfile 构建出 Docker镜像,通过 Docker 镜像运行Docker容器。

我们可以从 Docker 容器的角度,来反推三者的关系,如下图:

微信截图_20230105161930.png

2、Dockerfile

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明,它是构建镜像的关键。

一个 Docker 镜像包含了很多只读层,每一层都由一个 Dockerfile 指令构成,这些层堆叠在一起,每一层都是前一层变化的增量。例如:

FROM ubuntu:18.04COPY . /appRUN make /appCMD python /app/app.py

每条指令都会创建一层:

  • FROM:从 ubuntu:18.04 Docker 镜像创建了一层,也作为基础镜像层。
  • COPY:从 Docker 客户端的当前目录添加文件。
  • RUN:执行 make 命令.
  • CMD:指定要在容器中运行的命令。

上述就是一个简单的 Dockerfile 文件,再通过 docker build -t 命令便可直接构建出镜像。

在这里就不过多介绍 Dockerfile 的各个指令的用法,更多更详细的可参考:Dockerfile reference

3、Dockerfile 的最佳实践

本节将列举出一些最佳实践技巧,来帮助我们更好的写好 Dockerfile。

3.1 尽可能使用官方镜像作为基础镜像

Docker 镜像是基于基础镜像构建而来,因此选择的基础镜像越恰当,我们要做的底层工作就越少。比如,如果构建一个 Java 应用镜像,选择一个 openjdk 镜像作为基础比选择一个 alpine 镜像更简单。

尽可能使用当前的官方镜像作为基础镜像,无论是从镜像大小,还是安全性来讲,都是比较可靠的。

下面的一些镜像,可根据使用场景来选择合适的基础镜像:

镜像名称 大小 说明和使用场景
busybox 754.7 KB 一个超级简化版嵌入式 Linux 系统。临时测试用。
alpine 2.68 MB 一个面向安全的、轻量级的Linux系统,基于musl libc 和 busybox。主要用于测试,也可用于生产环境。
centos 79.65 MB 主要用于生产环境,支持CentOS/Red Hat,常用于追求稳定性的企业应用。
ubuntu 29.01 MB 主要用于生产环境,常用于人工智能计算和企业应用。
debian 52.4 MB 主要用于生产环境。
openjdk 161.02 MB 主要用于 Java 应用。

3.2 减少 Dockerfile 指令的行数

Dockerfile 中每一行指令都代表了一层,多一层都可能带来镜像大小变大。

因此,在实际编写 Dockerfile 时,可以将同类操作放在一起来避免多行指令,更有助于促进层缓存。比如将多条 RUN 操作进行合并,并用 ;\\ 或者 && 连接在一起。

(减少指令行数,并不意味着越少越好,需要从改动频繁程度来决定是否合并为一条指令。)

例如下面的 Dockerfile,会执行多条命令,通过 ;\\ 连接将其用一条 RUN 指令来完成。

FROM node:6.14LABEL MAINTAINER xcbeyondRUN npm install gitbook-cli -g;\\
   gitbook -V; \\
   npm install svgexport -g --unsafe-permCMD ["/bin/sh"]

3.3 改动不频繁的内容往前放

对于 Docker 镜像而言,每一层都代表了 Dockerfile 中的一行指令,每一层都是前一层变化的增量。例如一个 Docker 镜像有ABCD 四层,B 层修改了,那么 BCD 都会变化。

因此,在编写 Dockerfile 时,尽量将改动不频繁的内容往前放,即:将系统依赖往前写,因为像 apt, yum 这些安装的东西,是很少修改的。然后写应用的库依赖,比如 pip install,最后 copy 应用,编译应用。

例如下面这个 Dockerfile,就会在每次代码改变的时候都重新 Build 大部分层,即使只改了一个页面的标题。

FROM python:3.7-buster # copy sourceRUN mkdir -p /opt/appCOPY myapp /opt/app/myapp/WORKDIR /opt/app# install dependencies nginxRUN apt-get update && apt-get install nginxRUN pip install -r requirements.txtRUN chown -R www-data:www-data /opt/app # start serverEXPOSE 8020STOPSIGNAL SIGTERMCMD ["/opt/app/start-server.sh"]

我们可以改成,先安装 Nginx,再单独 copy requirements.txt,然后安装 pip 依赖,最后 copy 应用代码。

FROM python:3.7-buster # install dependencies nginxRUN apt-get update && apt-get install nginxCOPY myapp/requirements.txt /opt/app/myapp/requirements.txtRUN pip install -r requirements.txt # copy sourceRUN mkdir -p /opt/appCOPY myapp /opt/app/myapp/WORKDIR /opt/app RUN chown -R www-data:www-data /opt/app # start serverEXPOSE 8020STOPSIGNAL SIGTERMCMD ["/opt/app/start-server.sh"]

3.4 编译和运行需分离

我们在编译应用时很多时候会用到很多编译工具、编译环境,例如:node、Golang 等,但是编译后,运行时却不再需要。这样的编译环境往往占用很大,使得镜像额外变大。

因此,可以将应用事先在某个固定编译环境编译完成,得到编译后的二进制文件,再将其 COPY 到镜像中即可,这样镜像中只包含应用的运行二进制文件。

例如下面这个 Dockerfile,将 Golang 程序编译好的二进制文件 app,构建到镜像中:

FROM alpine:latestLABEL maintainer xcbeyondWORKDIR /appCOPY app /appCMD ["/app/app"]

3.5 删除不需要的依赖项

Docker 镜像应该尽可能小。在编写 Dockerfile 时仅包含基本内容,不要引入无关内容,从而使得镜像大小更小、构建速度更快,并且减少受攻击的可能面。

镜像更小,也更利于存放到镜像仓库,减少网络带宽开销。

不要安装应用程序实际不使用的任何包、库。

3.6 避免凭证构建到镜像

这是最常见和最危险的 Dockerfile 问题之一。在构建镜像过程中,复制配置文件可能很诱人,但你切记可能会引入很大的安全隐患。

在 Dockerfile 中通过 COPY 指令将任何配置文件内容都复制到你的镜像,并且任何可以访问它的人都可以访问它。如果这个配置文件中,无意间包含了数据库密码配置,那么你就彻底将这些密码暴露给了所有使用该镜像的所有人。

为了避免这类问题,必须将配置密钥、敏感数据只能提供给具体的容器,而不是提供给构建它们的镜像。可使用环境变量、挂载卷等方式在容器启动时注入数据。这样就避免了意外的信息暴露,并确保你的镜像可跨环境重复使用。


感谢您的阅读,也欢迎您发表关于这篇文章的任何建议,关注我,技术不迷茫!

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 容器
    +关注

    关注

    0

    文章

    495

    浏览量

    22063
  • 镜像
    +关注

    关注

    0

    文章

    165

    浏览量

    10737
  • Docker
    +关注

    关注

    0

    文章

    461

    浏览量

    11860
收藏 人收藏

    评论

    相关推荐

    C编程最佳实践.doc

    C编程最佳实践.doc
    发表于 08-17 14:37

    Dockerfile最佳实践

    ”微服务一条龙“最佳指南-“最佳实践”篇:Dockerfile
    发表于 07-11 16:22

    Dockerfile使用规则

    Dockerfile编写规范
    发表于 08-12 14:30

    变量声明最佳实践

    所以我们开始编写32位和16位代码,并过渡到MPLAB X和XC编译器。我想到的一个主题是声明变量的最佳实践。常规IpType。h或类型。h pr STDIN。或It8或字节char等任何想法,走哪条路?
    发表于 09-30 12:01

    虚幻引擎的纹理最佳实践

    纹理是游戏不可或缺的一部分。 这是一个艺术家可以直接控制的领域,以提高游戏的性能。 本最佳实践指南介绍了几种纹理优化,这些优化可以帮助您的游戏运行得更流畅、看起来更好。 最佳实践系列指
    发表于 08-28 06:39

    MySql5.6性能优化最佳实践

    MySql5.6性能优化最佳实践
    发表于 09-08 08:47 13次下载
    MySql5.6性能优化<b class='flag-5'>最佳</b><b class='flag-5'>实践</b>

    全面详解Dockerfile文件

    Docker 可以通过读取 Dockerfile 中的指令自动构建镜像。Dockerfile 是一个文本文档,其中包含了用户创建镜像的所有命令和说明。 一、 变量 变量用
    的头像 发表于 09-22 15:38 1886次阅读

    DevOps最佳实践

      遵循上述最佳实践,组织可以开发和自动化其解决方案的交付过程,以有效地实现其业务目标。
    的头像 发表于 08-15 14:41 957次阅读

    镜像构建Dockerfile的介绍

    Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
    的头像 发表于 09-06 09:36 1151次阅读

    图像传感器处理和最佳实践

    图像传感器处理和最佳实践
    发表于 11-15 20:30 0次下载
    图像传感器处理和<b class='flag-5'>最佳</b><b class='flag-5'>实践</b>

    SAN管理最佳实践指南

    电子发烧友网站提供《SAN管理最佳实践指南.pdf》资料免费下载
    发表于 08-29 09:20 0次下载
    SAN管理<b class='flag-5'>最佳</b><b class='flag-5'>实践</b>指南

    SAN设计和最佳实践指南

    电子发烧友网站提供《SAN设计和最佳实践指南.pdf》资料免费下载
    发表于 09-01 11:02 0次下载
    SAN设计和<b class='flag-5'>最佳</b><b class='flag-5'>实践</b>指南

    Windows 10迁移的最佳实践

    电子发烧友网站提供《Windows 10迁移的最佳实践.pdf》资料免费下载
    发表于 09-07 15:37 0次下载
    Windows 10迁移的<b class='flag-5'>最佳</b><b class='flag-5'>实践</b>

    Dockerfile定义Docker镜像的构建过程

    了解Dockerfile Dockerfile 是一个文本文件,用于定义 Docker 镜像的构建过程。它以指令的形式描述了如何构建镜像,从基础镜像开始逐步添加配置、文件和依赖,最终形成我们所需
    的头像 发表于 09-30 10:22 2547次阅读

    如何使用dockerfile创建镜像

    Docker是一个开源的平台,用于快速构建、打包、部署应用程序的容器化工具。而Dockerfile是一个文本文件,包含了一组可自动化构建Docker镜像的指令。本文将详细介绍
    的头像 发表于 11-23 09:52 743次阅读