Docker——镜像(如何上手构建一个自己的docker镜像)

一、镜像的结构(分层结构)

1 什么是base镜像?

  • base 镜像:(1)不依赖其他镜像,从 scratch 构建;(2)其他镜像可以之为基础进行扩展。

所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。

2 docker镜像的分层结构特点

  • 1.共享宿主机的kernel
  • 2.base镜像提供的是最小的Linux发行版
  • 3.同一docker主机支持运行多种Linux发行版
  • 4.采用分层结构的最大好处是:共享资源
  • 5.Copy-on-Write 写时复制技术,用于可写容器层
  • 6.容器层以下所有镜像层都是只读的
  • 7.docker从上往下依次查找文件
  • 8.容器层保存镜像变化的部分,并不会对镜像本身进行任何修改
  • 9.一个镜像最多127层

内核是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。用户空间的文件系统是 rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。 对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。 而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。

base镜像提供的是最小的Linux发行版同一docker主机支持运行多种Linux发行版 bootfs (boot file system) 主要包含 bootloader 和 kernel, bootloader主要是引导加载kernel, 当boot成功后kernel 被加载到内存中后 bootfs就被umount了。

rootfs (root file system) 包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。 由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别,
因此不同的发行版可以公用bootfs。比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。所以 Docker 可以同时支持多种 Linux镜像,模拟出多种操作系统环境。)
在这里插入图片描述

3 关于docker镜像的资源共享

比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base
镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。
这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc 是否也会被修改?答案是不会!

修改会被限制在单个容器内。

这就是我们接下来要说的容器Copy-on-Write (写时复制)特性

  • 新数据会直接存放在最上面的容器层。
  • 修改现有数据会先从镜像层将数据复制到容器层,修改后的数据直接保存在容器层中,镜像层保持不变。
  • 如果多个层中有命名相同的文件,用户只能看到最上面那层中的文件。

4 可写的容器层

  • 当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。
  • 典型的Linux在启动后,首先将 rootfs 置为 readonly, 进行一系列检查, 然后将其切换为 “readwrite” 供用户使用。在docker中,起初也是将 rootfs 以readonly方式加载并检查,然而接下来利用 union mount 的将一个 readwrite 文件系统挂载在 readonly 的rootfs之上,并且允许再次将下层的 file system设定为readonly 并且向上叠加, 这样一组readonly和一个writeable的结构构成一个container的运行目录, 每一个被称作一个Layer。
    在这里插入图片描述
    所有对容器的改动,无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。

5 容器层的细节

  • 上层文件覆盖:镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
  • 添加文件:在容器中创建文件时,新文件被添加到容器层中。
  • 读取文件:在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。
  • 修改文件:在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
  • 删除文件:在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。
  • 只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。也就是说,容器层写操作时,因为最上层容器没有这个文件,所以需要从下层镜像层copy一份上来,进行修改。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。这样就解释了我们前面提出的问题:容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。

二、镜像的构建

1 docker commit 构建新镜像

基本思路:运行容器——>修改容器——>保存容器为新的镜像

注意:ctrl+p+q 后台运行退出容器;ctrl+d直接退出容器,这样容器会直接停止运行

[root@server1 ~]# docker load -i ubuntu.tar
[root@server1 ~]# docker images
[root@server1 ~]# docker run -it --name vm1 ubuntu
root@11aea71d877a:/# uname -r   #查看内核版本,与素主机内核版本相同,内核不在镜像分层结构内。
root@11aea71d877a:/# touch file{1..100}   #新建100个文件,容器层,都不会影响到镜像层
root@11aea71d877a:/# ls
[root@server1 ~]# docker ps -a   #查看所有进程

此时,我们对docker容器进行了修改,也就是容器层有了变化,如何保存呢?docker commit

[root@server1 ~]# docker commit vm1 ubuntu:v1   #有100个文件,把vm1容器的数据提交到镜像,并且给镜像起个名字
[root@server1 ~]# docker history ubuntu:v1    #可以查看镜像层,有多少层
[root@server1 ~]# docker history ubuntu:latest   #查看历史,除最上边之外和v1相同
[root@server1 ~]# docker run -it --name vm2 ubuntu:v1  #此时,该容器有100个文件

使用commit的缺点是:效率低、可重复性弱、容易出错,使用者无法对镜像进行审计,存在安全隐患

2 Dockerfile构建镜像

Dockerfile的基本写法:

FROM:指定base镜像,如果本地不存在会从远程仓库下载。	

MAINTAINER:设置镜像的作者,比如用户邮箱等。

COPY:把文件从build context复制到镜像 (都是基于dockerfile的当前目录)
支持两种形式:COPY src dest 和 COPY ["src", "dest"]
src必须指定build context中的文件或目录

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,如果指定了很多,只有最后一个有效。
  • 使用Dockerfile构建一个Apache应用:
  1. docker引擎构建目录。尽量不要放在根目录下,否则会把根目录下所有数据发给docker引擎。
[root@server1 ~]# ls
docker  game2048.tar  rhel7.tar  ubuntu.tar
[root@server1 ~]# docker load -i rhel7.tar    #导入镜像

[root@server1 ~]# mkdir docker    #docker引擎构建目录。尽量不要放在根目录下,否则会把根目录下所有数据发给docker引擎。
[root@server1 ~]# cd docker/
[root@server1 docker]# vim Dockerfile
  1. 该目录下写dockerfile,以及需要copy的文件。
FROM rhel7
COPY dvd.repo /etc/yum.repos.d/
RUN rpmdb --rebuilddb &&  yum install httpd -y
EXPOSE 80
VOLUME ["/var/www/html"]
CMD ["/usr/sbin/httpd","-D","FOREGROUND"]
[root@server1 docker]# vim dvd.repo
[dvd]
name=rhel7.3
baseurl=http://172.25.11.250/rhel7.3
gpgcheck=0
  1. 使用dockerfile构建apache镜像
[root@server1 docker]# docker build -t rhel7:apache .    #最后的点表示从当前目录
[root@server1 docker]# docker run -d --name vm2 -p 80:80    rhel7:apache 
  1. 查看
docker inspect vm2  	查看容器信息
cd /var/lib/docker/volumes/fb0f95afa6602832686368f9f9c35020c2cd84759010678d8d4f24fef033c361/_data/  
# 容器的真实数据卷
vim index.html
cat index.html 
curl 172.17.0.3
docker volume ls 
docker volume prune 	# 删除所有非活跃的容器数据卷

三、构建镜像的优化

1.减少分层(多阶段构建镜像)

尽量减少镜像层数,清理中间产物,多阶段构建镜像。
Dockerfile内容:

FROM rhel7 as build
COPY dvd.repo /etc/yum.repos.d/
ADD nginx-1.15.8.tar.gz /mnt
EXPOSE 80
WORKDIR /mnt/nginx-1.15.8
RUN rpmdb --rebuilddb && yum install -y gcc make zlib-devel pcre-devel && rm -fr /var/cache/yum && sed -i 's/CFLAGS="$CFLAGS -g"/#CFLAGS="$CFLAGS -g/g' auto/cc/gcc && ./configure --prefix=/usr/local/nginx && make && make install && rm -fr /mnt/nginx-*

FROM rhel7
COPY --from=build  /usr/local/nginx   /usr/local/nginx
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;"]

这里实际镜像层就四层。

[root@server1 docker]# docker build -t rhel7:nginx .
[root@server1 docker]# docker history rhel7:nginx 
[root@server1 docker]# docker run -d --name vm1 -p 80:80 rhel7:nginx   #因为容器分配到的ip跟物理机不在同一网段,所以必须作端口映射才能在物理机访问。

2. 选择最精简的基础镜像。

镜像的目的最后是封装应用,那么我们为了实现run anywhere,当然在实现功能的基础下,越小越好,方便容器迁移。

docker load -i distroless.tar
docker load -i nginx.tar
vim Dockerfile
写入:
FROM nginx:1.16 as base

# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
ARG Asia/Shanghai

RUN mkdir -p /opt/var/cache/nginx && \
    cp -a --parents /usr/lib/nginx /opt && \
    cp -a --parents /usr/share/nginx /opt && \
    cp -a --parents /var/log/nginx /opt && \
    cp -aL --parents /var/run /opt && \
    cp -a --parents /etc/nginx /opt && \
    cp -a --parents /etc/passwd /opt && \
    cp -a --parents /etc/group /opt && \
    cp -a --parents /usr/sbin/nginx /opt && \
    cp -a --parents /usr/sbin/nginx-debug /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libpcre.so.* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libz.so.* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libc.so.* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libdl.so.* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libpthread.so.* /opt && \
    cp -a --parents /lib/x86_64-linux-gnu/libcrypt.so.* /opt && \
    cp -a --parents /usr/lib/x86_64-linux-gnu/libssl.so.* /opt && \
    cp -a --parents /usr/lib/x86_64-linux-gnu/libcrypto.so.* /opt && \
    cp /usr/share/zoneinfo/${TIME_ZON:-ROC} /opt/etc/localtime

FROM gcr.io/distroless/base
COPY --from=base /opt /
EXPOSE 80 443
ENTRYPOINT ["nginx", "-g", "daemon off;"]

docker build -t rhel7:v2 .
docker history rhel7:v2

ok ,至此成功!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值