docker(二)镜像构建、Dockerfile及镜像优化

 一、 docker镜像详解

1. 镜像的分层结构

  •  共享宿主机的kernel
  • base镜像提供的是最小的Linux发行版
  • 同一docker主机支持运行多种Linux发行版
  • 采用分层结构的最大好处是:共享资源

镜像通过分层,如果本地已经有了,不管这一层属于哪个镜像,因为每一层都有独立的标识(都是唯一的),只要docker判断有这一层,那它就不会进行重复的拉取。包括在上传仓库的时候也是一样的,仓库内存储时也是按照层来存储的,如果远程仓库有这一层了,就不需要重复上传了,节省了带宽。

 

docker使用 Copy-on-Write 的机制(可写容器层),当我们想保存一个数据的时候,由于镜像是只读的,当我们创建容器时,是在镜像层的上面创建一个可写容器层,所有对容器的修改都会放置在可写容器层上,只要这个容器不被释放,这个数据一直存在,除非把这个容器删掉。如果想保存,把可写容器层进行打包,即把它创建成一个镜像层,一旦成为镜像层它就变成只读模式了(最多127层)。

2.镜像的表示

 镜像的表示分为四部分:红色的部分是镜像中心域名,黄色的部分是镜像命名空间,我们可以根据命名空间进行权限控制等操作,绿色是镜像的名称,每个镜像有一个版本(即标签)。Docker官方的镜像不需要镜像中心的域名,有一些镜像可以省略命名空间。

base 镜像简单来说就是不依赖其他任何镜像,完全从0开始建起,其他镜像都是建立在他的之上,可以比喻为大楼的地基,docker镜像的鼻祖。
base 镜像有两层含义:
    (1)不依赖其他镜像,从 scratch 构建;
    (2)其他镜像可以之为基础进行扩展。
所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,
比如 Ubuntu, Debian, CentOS 等。

二、docker的构建

1、commit 提交

 docker commit 构建新镜像三部曲

  • 运行容器 
  • 修改容器
  • 新的镜容器保存为镜像

使用docker info 可以看到此时我们用的是docker官方的仓库;

拉取一个具有一些小工具的镜像:

 由于busybox是基础镜像不是应用镜像,基础镜像不像应用镜像可以打到后台,只能以交互式方式打开:

使用下面这个指令可以看到容器的构建历史:

使用docker inspect 可以查看更详细的信息。

不用的容器使用 docker rm -f 删掉:

 创建容器并进入容器:

 -it:以交互式的方式创建容器

此时我们就可以进入容器进行操作了:

 使用 ctrl+d 就退出容器了,此时容器会被stop掉,但是并不会被删除:

 我们再启动容器就可以再次进入容器:

我们还可以使用 ctrl+p+q 三个键同时按下,就可以将其打入后台运行而不是stop掉: 

我们也可以手动stop掉容器:

我们将刚刚创建的容器删除再进去,就会发现我们刚刚创建的文件已经没有了:

 这是因为我们在容器层把这个容器释放掉以后就没有了,如果我们想将数据保存下来,就需要将我们自己的镜像打包

对比源镜像和我们自己打包的镜像,只比源镜像多了一层,就是我们刚刚创建文件的命令:

 此时用我们刚刚提交的镜像创建容器,可以发现刚刚创建的文件还在:

 删除镜像:

commit提交的缺点:我们发现在删除时它也只删除了一层,就是我们刚刚创建文件的那一层,基础的busybox镜像它是不会删除的。我们发现这种构建方式不是非常方便,他的缺点有 效率低、可重复性弱、容易出错,使用者无法对镜像进行审计,存在安全隐患 。 

 无法审计: 当我们使用docker history 时无法看见对容器进行了什么具体的操作,只能看见一个sh命令

2.通过Dockerfile 提交(透明化)

dockerfile的创建原理—>相当于一个一个进行提交:

创建Dockerfile(注:名字只能是Dockerfile,因为默认读取的文件名称为Dockerfile):

基于什么基础镜像做什么操作:

建议将Dockerfile放在一个空目录下,因为在构建过程中可能要用到一些外部资源,将这些资源都放在这个空目录下,不要放在根下,因为在构建时,它会将目录下的所有文件都发给docker引擎去构建, 放在根下的话,发的数据就太大了。

注:构建时的 "." 意思是构建所需的所有文件都来自当前目录。此时就可以对镜像进行审计了,即镜像构建的步骤都能看见:

若此时我们只想进去查看或者操作一下镜像,退出就自动删除容器:

 而且使用Dockerfile创建镜像还有缓存特性,比如我们构建时遗忘了一层,我们再重新编辑Dockerfile:

可以看到在构建时,之前的三层都是 Using cache 使用缓存,只有新加的一层构建了:

 注:由于镜像是一层一层叠加的,如果我们改了第二层,那么后面的所有层都要重新构建。

Dockerfile 命令详解

dockerfile常用指令

  • FROM:指定base镜像,如果本地不存在会从远程仓库下载。
  • MAINTAINER:设置镜像的作者,比如用户邮箱等。
  • COPY:把文件从build context复制到镜像 支持两种形式:COPY src dest 和 COPY ["src", "dest"] src必须指定build context中的文件或目录(文件一定要放在Dockerfile同一目录下)
  • ADD 用法与COPY类似,不同的是src可以是归档压缩文件,文件会被自动解压到dest,也可以自动下载URL并拷贝到镜像: ADD html.tar /var/www ADD http://ip/html.tar /var/www
  • ENV 设置环境变量,变量可以被后续的指令使用: ENV HOSTNAME sevrer1.example.com
  • EXPOSE 如果容器中运行应用服务,可以把服务端口暴露出去: EXPOSE 80(对外暴露的端口)
  • VOLUME 申明数据卷,通常指定的是应用的数据挂在点: VOLUME ["/var/www/html"]
  • WORKDIR 为RUN、CMD、ENTRYPOINT、ADD和COPY指令设置镜像中的当前工作目录,如果目录不存在会自动创建。
  • RUN 在容器中运行命令并创建新的镜像层,常用于安装软件包: RUN yum install -y vim
  • CMD 与 ENTRYPOINT 这两个指令都是用于设置容器启动后执行的命令,但CMD会被docker run后面的命令行覆盖,而ENTRYPOINT不会被忽略,一定会被执行。 docker run后面的参数可以传递给ENTRYPOINT指令当作参数。 Dockerfile中只能指定一个ENTRYPOINT,如果指定了很多,只有最后一个有效。

COPY :

ADD (可以添加压缩包、远程地址): 

例如我们一个源码包放到镜像内,并将其自动解压:

此时进入镜像发现这个压缩包已经被自动解压了:

ENV: 

 EXPOSE:

此参数一般使用某些服务时有用,例如nginx 80端口、443端口。并且在创建容器时使用 -p 选项指定端口。-P 暴露所有端口。

VOLUME:

当在集群中数据量很大时,用来分离容器和数据,把数据持久化到本地或者远程。

 此时我们的数据卷还是空的:

当我们在容器中的data中建立数据后:

多了一个卷:

 用docker inspect demo查看详细信息,可以看到为我们随机创建了一个卷,而卷后面跟的 ../_data会挂接到容器内的/data,即相当于在容器内创建的文件自动持久化到了宿主机:

若我们在宿主机中将其删掉,再创建一个,那么容器内的数据也就变更了: 

 

删除卷:在删除容器时卷并不会被删除

需要手动删:

CMD:

 

 此时我们不需要交互,只需要将镜像运行一下:

官方建议使用下面这种写法:

但是这种方式不会调用底层的shell,即不能调用本机的变量:

如果改成 [ ]  即exec方式,就不能调用变量了。我们需要用下面这种写法才能调用变量:

可以看到调用正常。 

Shell和exec格式的区别 # cat Dockerfile

FROM busybox

ENV name world

ENTRYPOINT echo "hello, $name"

Shell格式底层会调用/bin/sh -c来执行命令,可以解析变量,而下面的exec格式不会:

# cat Dockerfile

FROM busybox

ENV name world

ENTRYPOINT ["/bin/echo", "hello, $name"] 

ENTRYPOINT:

当我们在镜像后面传参时:

这是因为 run 参数后面可以跟命令参数的,且CMD是可以被覆盖的,而 ENTRYPOINT 不能被覆盖,所以 Hello 还在,而 world 不见了。

3.使用Dockerfile 构建nginx

接下来我们基于centos7的镜像,用Dockerfile的方式,自己封装一套应用镜像nginx:

 我们可以使用docker inspect nginx 来查看官方nginx是怎么封装的:

我们先把之前创建的demo镜像全部删掉:

 自己编写Dockerfile 安装nginx:

构建:

可以看到已经构建成功了:

我们来试试能不能运行:

 查看详细信息,找到容器的IP地址:

访问成功:

 找到数据卷的位置:

 我们进入数据卷,并且替换掉nginx的默认发布目录:

可以看到再访问已经改变了,这说明容器的数据卷挂载在本地的磁盘上。

3.1 镜像的优化 

但是我们发现自己封装的nginx镜像,相比于官方nginx镜像来说太大了,我们可以对我们的Dockerfile构建过程进行优化。

 我们可以从以下几个方面来优化镜像

  • 选择最精简的基础镜像
  • 减少镜像的层数
  • 清理镜像构建的中间产物
  • 注意优化网络请求
  • 尽量去用构建缓存 :加快镜像运行速度
  • 使用多阶段构建镜像

优化1:

我们再进行构建: 

 可以看到demo:v2 比 demo:v1 小一点。

优化2:

分成两阶段,将构建阶段的nginx二进制程序拷贝到新的镜像中。

再构建v3:

 可以看到多阶段构建效果非常明显,最终的镜像只比源镜像大了4M。

优化3:

我们想要再减小镜像的大小就只能更换基础镜像了。上github搜索谷歌的包distroless:

 也可以通过docker搜索:

我们可以直接在github搜索 distroless nginx,找寻能用的Dockerfile:

 找到可用的Dockerfile 内容:

接下来我们建议一个新的目录,使用更小的基础镜像建立nginx镜像:

  注:此操作需要版本base-debian 和 nginx版本匹配。若已经打包镜像,但是启动镜像时,发现启动不起来,可通过docker logs demo 查看日志。

在官网下载基础镜像 base-debian10 ,并将其导入成镜像:

 拉取nginx:1.18.0 :

构建demo:v4:

可以看到我们的镜像能正常运行,说明镜像没有问题:

也能正常访问:

 而现在我们构建的demo:v4 只有31.7M ,远比官方的镜像小:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值