"我本就是不朽,何必妥协迁就?"
Docker镜像制作
有时候我们仅仅使用官方镜像资源,就可以完成我们的业务需求。但实际中,也会出现官方镜像无法满足需求,需要我们通过一定的手段 ------ "自定义镜像"。
制作镜像往往是因为以下原因:
👑 编写的代码,打包到镜像中跟随镜像发送
👑 第三方制作的镜像存在不足,安全性差
👑 特定需求或者功能无法满足 等等
Docker镜像制作的方式分为两种:
💍 通过制作快照的方式,获取当前容器中运行的镜像。
💍 使用Dockerfile方式构建。能够将构建的过程记录,自动化进行配置。
Docker制作命令
🏀 docker commit
功能: 从容器中创建一个新的镜像
语法:
bash
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
OPTIONS:
-a, --author string 提交镜像的作者
-c, --change list 使用dockerfile指令 创建镜像
-m, --message string 提交信息
-p, --pause 提交时,暂停容器
使用快照制作镜像
C++ Hello World:
创建一个临时目录,这个目录
编写C++代码:
启动一个centos7容器:
要想运行一个C++代码,我们大概需要安装编译C++代码所需的 编译器、标准库。可是,默认Centos中的yum源是国外的,所以我们还需要配置一下国内源。我们可以直接在这里进行配置:
CentOS 源使用帮助 --- USTC Mirror Help 文档
安装g++编译工具:
我们将写好的代码,复制拷贝进centos7这个容器:
编译运行后,将这个容器提交镜像:
测试镜像,看看是否能运行:
使用Dockerfile 镜像制作
Dockerfile简介:
是一个用于构建 Docker 镜像的文本文件。它包含了构建镜像所需的一系列指令和说明,这些指令定义了如何将单个层的构建过程分解成更小的步骤。
每个指令对应于镜像中某一层的一个特定操作: 创建文件、复制文件、设置环境变量或者运行某个命令等
Dockerfile格式:
该指令不区分大小写,但是我们使用大写表示命令、小写表示参数用以区分。
bash
#Comment
RUN echo "Hello This is a Dockerfile Step"
为什么选择Dockefile:
Dockerfile制作镜像的过程,就像从平地起的高楼。每一个楼层的建设,都离不开Dockerfile指令的指示。我们使用Dockerfile可以得到如下的好处:
🎨 按需自定义镜像:
不过这个特性与快照制作一样.都可以解决官方镜像可能无法满足需求的情况
🎨 很方便自动化构建,重复执行:
Dockerfile可以自动化完成镜像的构建。区别于docker commit一个一个手动地执行命令,它可以重复进行执行,而使用docker commit 你需要记录构建镜像过程中的命令。
🎨 维护方便,不再是黑盒操作:
🎨 更加标准化,体积可以做得更小:
docker容器启动后,系统就会产生出很多临时文件,这些临时文件会随 docker commit存储在镜像之中,导致镜像文件臃肿。使用Dockerfile可以根据一系列的策略,如 多级构建、将编译和构建分开,不会有运行时的多余文件。
Dockerfile命令清单:
|-------------|--------------------------------------|
| FROM | 基于哪一个镜像文件构建 |
| MAINTAINER | 镜像维护者的信息 |
| LABEL | 为镜像添加元数据 |
| COPY | 拷贝文件或目录到镜像之中,同ADD类似。但不具备自动下载和解压的功能 |
| ADD | 拷贝文件或目录到镜像之中。如果是URL 或 压缩包就会自动下载或解压 |
| WORKDIR | 指定工作目录 |
| RUN | 指定在docker build过程中运行的程序 |
| VOLUME | 指定容器挂载 |
| EXPOSE | 声明容器的服务端口 |
| ENV | 设置环境变量 |
| CMD | 运行容器执行的命令 |
| ENTRYPOINT | 运行容器时的入口 |
| ARG | 指定构建时的参数 |
| SHELL | 指定采用shell |
| USER | 指定当前用户 |
| HEALTHCHECK | 健康检测指令 |
| ONBUILD | 当前构建镜像时不会执行。当以当前镜像为基础,去构建下一个镜像时才会被执行 |
| STOPSIGNAL | 允许覆盖发送到容器内默认信号 |
这些镜像命令参数完完全全可以通过Docker官网中查询使用方式,本篇不再过多赘述....
C Hello World:
通过编写Dockerfile完成一个简单的镜像制作。
创建目录:
创建C语言代码的demo:
编写dockerfile,你可以很清晰地看到,这就是将所谓的 "快照" 记录的过程。但通过dockerfile还可以设置其他东西......
执行构建:
这里我们有意在dockerfile中的文件写错命令,执行构建时就会报错。我们将错误处进行修改后,重新构建:
允许容器,查看结果。因为我们在Dockerfile中设置了启动命令 ------ 执行我们的C语言代码,所以,我们一定能够看到当容器允许时,终端会打印字符:
快照制作 vs dockerfile制作
我们能够清晰地看到,同样的过程,使用快照制作镜像的大小是远大于dockerfile的。并且,我们还能通过优化dockerfile的编写,使得镜像大小更精简......
Dockerfile编写优化甜点
善用 .dockerignore文件
使用.dockerignore文件类似于我们使用git时,通过配置.gitignore过滤掉我们不想提交到代码仓库中的文件。
在docker中,为了避免发送一些不必要的内容,可以配置.dockerignore文件,从而加速镜像构建的过程。
建立Dockerfile目录,编写Dockerfile:
创建.dockerignore文件,并忽略以.txt为后缀的文件:
构建镜像,我们可以在镜像中查看应该被我们拷贝进 镜像文件的.cc文件,而以.txt结尾的问价你是看不到的!
镜像构建的多阶段
通过多步骤地创建,可以将编译与运行等过程分开,保证最终镜像只包括运行应用所需要的最小化环境。用户还可以通过分离编译镜像和运行镜像。
构建docker镜像可以有以下两种方式:
🥎 将全部组件以及依赖库的编译、测试、打包等流程统一都封装在一个docker镜像之中。其存在的问题在于,编写Dockerfile过长,维护性低,镜像层次多体量大。
🥎 将每个阶段分散到多个dockerfile 之中。一个dockerfile用于安装项目依赖库、编译测试,并将运行文件拷贝至运行环境之中。这种方式需要我们编写多个 dockerflie、自动化命令来协调整合这些部分。
方法一:
我们假设有一个C语言程序,需要Docker编译成可执行文件,并执行该文件:
编写dockerfile这同我们前面dockerfile制作类似,不再过多赘述:
构建镜像,查看大小:
方法二:
可以看到,单独使用dockerfile编译出的镜像大小有800MB+ 十分大。但,当我们实际编译完成test.c之后,并不需要这么大的一个GCC环境,一个小的运行环境即可。这时候我们就选择使用多阶段构建解决这个问题~
第一阶段:
第二阶段:
--from: 从base中COPY文件数据,这条路径如果在dest不存在也会进行create
构建容器,并测试结果看看:
我们还可以进一步选择比较小的基础镜像进行优化,如:alpine、busybox、debian等,这里我们一busybox为例,再次构建:
再次构建,测试容器,我们得到了惊人的镜像大小:
如何理解节省的空间? 我们编译使用的软件、以及换装成更轻量的busybox替换centos,这些会占用大量存储的镜像没有跟随我们的dockerfile,写入到咱们的运行态中!
那我们之前执行的FROM去哪里了呢?别忘了,最后一条FROM可以复用之前构建阶段生成的文件,多个FROM最终有效的只有最后一条~
多阶段构建可以很好解决镜像的层次多、体积大、部署时间长、维护性低等问题~
合理使用缓存
在镜像构建的过程之中,Docker会根据Dockerfile指定的顺序去执行每一个命令。在执行每条命令之前,Docker都会在缓存中查找,是否存在可用镜像。如果存在,就不会重复构建。Docker中的每一个指令,都会产生一层layer,一旦存在某层layer不一样,后面的就不能产生复用。
在"docker build"中,携带上"--no-cache=true"的选项,就不会去复用存在缓存的layer,而是重新构建。但,为了加快镜像的构建速度,一般我们不会携带这个选项。
我们仍然以这份装载了C语言容器的镜像为例:
首次构建镜像,花费的时间挺长的:
这里,我们再更改以下demo中的源码,再进行构建:
这是我们第二次进行构建了,不是说可以利用缓存吗?缓存到了哪个地方?! 别忘了,咱们对源文件demo.c动了手脚!因为这一条 "COPY demo.c"进行了修改,那么其后的所有指令都不会复用之前的缓存。
因此,"不变的内容写在前,变的内容写在后~"。
我们重新构建镜像,即便针对了demo.c文件内容的修改,也可以复用缓存:
镜像构建时间,得到了极大的缩短!
合理使用cache,减少内容目录下的文件。内容不变的指令都放在前面,这样可以尽量复用~
基础镜像尽量使用官方镜像,并选择体积较小的
容器的核心是应用。选择过大的父镜像最终会造成应用镜像的臃肿。可以使用一些瘦身镜像如:node:slim,或者小巧的系统镜像(alpine、busybox、debian)等
减少镜像层数
尽量合并COPY、RUN、ADD等指令。多个RUN可以合并成一条指令。
如:yum install -y sl && sl
精简镜像用途
让每一个镜像单一应用,避免构造复杂多功能镜像。
减少外部源干扰
数据如果是来源于外部引用,需要指定永久地址,携带版本信息,复用是不会出错。
减少不必要的包安装
安装需要的包,减少不必要的包安装。
镜像制作常见问题
📐 ADD 与 COPY的区别?
ADD 不仅仅能够将构建命令所在的本地主机上的文件、目录,还能将URL或压缩包所对应的文件,作为资源复制给 镜像文件系统。ADD就是增强版的COPY。
COPY 指令仅能将构建命令所在的本地主机上的文件、目录复制给镜像文件中。
📐 CMD 与 EntryPoint的区别?
ENTRYPOINT 中包含了容器启动后的执行命令。使得容器表现得就像一个可执行程序一样。与CMD区别的是不可以被docker run覆盖,会将其后跟的指令当做参数接收。只能指定一个Dockerfile,如果指定多了也只会生效最后一个。如果需要在 docker run时修改entrypoint命令,只需要携带上 -entrypoint参数。
一般会组合使用这两个命令,EntryPoint执行默认允许命令,CMD作为参数传递给它。
📐 多个FROM指令如何使用?
多个FROM并不是为了生成多根关系,其生成的镜像仍然是以最后一条FROM为准,之前的FROM都会被抛弃!那么之前的执行之前的FROM是为了什么?
每一条FROM都是一个构建阶段,所以在多构建阶段下,最后一条FROM可以复用之前构建阶段生成的文件。最大的使用场景就是 将编译环境和允许环境分离。
📐 快照与dockerfile的区别?
快照 是在Docker中创建镜像的一种方式,它是基于正在运行的容器创建的。当你对一个正在运行的容器进行快照时,Docker会保存该容器当前的状态,包括其文件系统和元数据。然而,请注意,快照只会保留当前的快照状态,它会丢弃所有的历史记录和元数据信息,这个过程类似于docker import\export。
Dockerfile是用于定义如何构建Docker镜像的文本文件。你可以通过编写Dockerfile来指定要在镜像中包含哪些程序、配置和文件,以及这些内容的来源。Dockerfile允许你在构建过程中执行命令,以定制你的镜像。
📐 什么是空悬镜像?
仓库名、标签均为<none>的被称之为虚悬镜像。一般来说,虚悬镜像失去了存在的价值,可以随时删去。
虚悬镜像出现原因:
🧸 原本拥有自己的镜像名和标签,但人为通过docker pull后发现新的版本,旧的镜像名被转移到了新的下载镜像身上。
🧸 在镜像制作的过程中,可能导致标签名被转移。
查看本地虚悬镜像:
bash
docker image ls -f dangling=true
📐 中间层镜像是什么?
为了加速镜像构建、重复利用资源,Docker会利用中间层镜像。默认的 "docker image ls"列表中只会显示顶层镜像,如果希望显示包括中间层镜像,需要携带参数"-a"。
与虚悬镜像不同,这些无标签的镜像都是中间层镜像。而这些镜像不应该被删除,否则就会导致依赖这些层的镜像出错。
本篇到此结束,感谢你的阅读。
祝你好运,向阳而生~