1.容器镜像
容器镜像是容器的模板,容器是镜像的运行实例, runtime根据容器镜像创建容器;容器镜像挂载在容器根目录下,是为容器中的应用提供隔离后执行环境的文件系统。
base镜像
我们希望能提供一个基本的操作系统环境,用户可以根据需要安装和配置软件,这样的镜像我们称作base镜像;能称作base镜像的通常都是各种Linux发行版的Docker镜像,比如 Ubuntu、Debian、CentOS等,其他镜像可以以之为基础进行扩展。

注:base镜像从scratch构建,不依赖于其他镜像;scratch是Docker官方提供的一个空镜像。
①、实现机制:
Ⅰ. Linux操作系统由内核空间和用户空间组成,其中内核空间是kernel, Linux在刚启动时会加载bootfs文件系统,启动完成之后bootfs会被卸载掉;用户空间的文件系统是rootfs, 包含/dev、/proc等目录。不同Linux发行版的区别主要就是rootfs,所以可以在同一台host上部署不同Linux发行版的base镜像。
Ⅱ. 对于base镜像来说,底层直接用Host的kernel, 自己只需要提供rootfs就行了(即打包应用及其运行所需要的所有依赖包),所以相对于虚拟机来说更加轻便。
注:base镜像在用户空间与发行版一致,但是内核的版本和Docker host主机的kernel版本一致;容器只能使用host的kernel,但是不能修改。
镜像分层结构
Docker支持通过扩展现有镜像来创建新的镜像,也就是说所有镜像层联合在一起,即为一个联合文件系统,这类文件系统会把多个目录(可能对应不同的文件系统)挂载到同一个目录,对外呈现这些目录的联合,也就是说用户在容器层中只能看到最上层的文件,最终呈现出一个分层的结构;
注:联合文件系统UnionFS将所有的镜像层联合挂载到一个统一的挂载点(镜像层文件系统中不同的目录进行“合并”),最终表现为一个Linux操作系统供容器使用。
可以最大化实现资源共享:当多个镜像都是从同一个base镜像构建而来,内存中只需加载一份base 镜像,就可以为所有容器服务;通过写前拷贝COW技术来实现资源共享。
原理简单阐述就好比:当父进程 fork 子进程时,内核并没有为子进程分配内存(当然基本的进程控制块、堆栈还是需要的),而是让父子进程共享内存;当两者之一修改共享内存时,会触发一次缺页异常导致真正的内存分配(此时才会为父子进程分配独享内存)。

1、联合文件系统实现机制:
①、首先需要内核支持OverlayFS特性;
$ cat proc/filesystems | grep overlaynodev overlay
②、创建分层的工作目录(相当于除了容器层以外的其他层);
$ mkdir base$ echo "layer 1" > base/metadata$ echo "hello,docker!" > base/data$ mkdir overlay$ echo "layer 2" > overlay/metadata$ echo "hello,k8s!" > overlay/data2
③、创建顶层目录(相当于容器层),以及OverlayFS所必需的work工作目录、文件系统挂点;
$ mkdir top work point
④、将分层以overlay文件系统类型挂载到挂载点;
$ mount -t overlay overlay \-o lowerdir=overlay:base,upperdir=top,workdir=work point#-t:指定文件系统类型#-o:指定挂载选项,lowerdir指定底层目录(从高层向低层指定),upperdir指定顶层目录(也就是容器层),workdir指定工作目录#最后指定挂载点
⑤、在挂载点目录下可以看到联合目录,目录所处的层级是很重要的,上层的文件会覆盖同名的下层文件;
#可以看到挂载点处显示出的联合目录$ ll */*-rw-r--r-- 1 root root 14 ... base/data-rw-r--r-- 1 root root 10 ... base/metadata-rw-r--r-- 1 root root 11 ... overlay/data2-rw-r--r-- 1 root root 10 ... overlay/metadata-rw-r--r-- 1 root root 14 ... point/data-rw-r--r-- 1 root root 11 ... point/data2-rw-r--r-- 1 root root 10 ... point/metadata#查看具有同名的上下层文件metadata$ cat point/metadatalayer 2 #内容为overlay层的metadata文件内容,而base层的metadata文件内容被覆盖
⑥、当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被称作“容器层","容器层”之下的都叫“镜像层”,只有容器层是可写的,容器层下面的所有镜像层都是只读的;修改文件时同理,当在容器中修改已存在的文件时,Docker会从上往下依次在各镜像层中查找此文件,一旦找到,立即将其复制到容器层(top层),然后再修改;
此处仅做目录模拟,所以其他镜像层仍是可写的。
#当向point目录中写入数据时,会写入到top目录(顶层目录)中$ echo "top layer" > point/frame$ ll */*...-rw-r--r-- 1 root root 10 ... point/frame-rw-r--r-- 1 root root 10 ... top/frame$ cat top/frame #查看容器层写入的文件内容,即为写入到挂载点同名文件中的内容top layer#修改point/data文件内容$ cat point/datahello,docker!test modify#然后会在top顶层目录中新增一个同名文件,内容即为修改后的内容$ ll */*...-rw-r--r-- 1 root root 10 ... top/data$ cat top/datahello,docker!test modify#查看底层base/data文件,发现内容并未改变$ cat base/datahello,docker!
⑦、当在容器中删除文件时, Docker 也是从上往下依次在镜像层中查找此文件。找到后,UnionFS会在容器层创建一个"whiteout"文件将被删除的文件进行遮挡,并且在容器层会创建一个同名的、主次设备号均为0的字符设备,文件不会被删除(空间不一定会被回收);如果制作 image 时使用到了一些关键的信息(用户名、密码等),则需要在所在层删除,不然这些信息依然会存在于 image 中。
c--------- 1 root root 0, 0 ... top/data
在线获取镜像
❶、安装docker-ce:以Centos系统为例,首先要获取docker-ce的yum源,此处使用的是华为云提供的repo;然后安装docker-ce
$ wget -O /etc/yum.repos.d/docker-ce.repo \https://repo.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo$ sudo sed -i 's+download.docker.com+repo.huaweicloud.com/docker-ce+' \/etc/yum.repos.d/docker-ce.repo #修改获取镜像的地址#如果是Centos8版本的系统,则需要首先卸载掉podman及container相关组件$ dnf remove podman$ dnf remove buildah$ dnf install docker-ce
❷、配置Docker镜像加速器:如果使用公共镜像仓库,由于是在国外网速限制,须要通过镜像加速站来获取镜像,以下以华为云平台镜像加速站配置为例(需提前注册华为云账号,并在控制台中找到容器镜像服务)
当镜像仓库使用"https",使用时则需要先进行登录验证,然后才能从镜像仓库拉取镜像;当镜像仓库使用"http"(或私有镜像仓库,直接使用IP地址),则不需要,可以直接拉取使用。
$ vim /etc/docker/daemon.json #如果该文件没有,则需要手动创建{"registry-mirrors": ["https://******.myhuaweicloud.com"]}$ systemctl daemon-reload #重新加载daemon服务器配置文件$ systemctl restart docker.service
❸、首先须要登录获取访问权限,u是用户,p是密码(在个人控制台自动生成)
$ docker login -u *** -p *** swr.cn-north-1.myhuaweicloud.com
❹、拉取镜像
$ docker pull httpd:2.42.4: Pulling from library/httpd...Pull completeStatus: Downloaded newer image for httpd:2.4$ docker image listREPOSITORY TAG IMAGE ID CREATED SIZEhttpd 2.4 683a7aad17d3 2 months ago 138MB
分析镜像分层
可以使用dockerviz工具来分析Docker image。
1、首先将dockviz.tar包(从百度网盘中下载)上传到镜像仓库;
$ docker load -i dockviz.tar
2、然后在 /etc/bashrc文件中最后一行添加命令别名,保证每次执行dockviz命令时都会临时运行一个dockerviz的容器来执行相应的动作;(退出重新登录后生效)
$ alias dockviz="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock dockviz:latest"
3、执行以下命令,让镜像文件以分层的形式保存在images.png图片中
#将所有的镜像信息以png的格式保存到images.png文件中$ dockviz images -d | dot -Tpng -o images.png
4、使用sz命令下载到本地Windows查看(rz上传文件、sz下载文件)
$ sz image.png
2. 构建镜像
Docker提供了两种构建镜像的方法:docker commit命令与Dockerfile构建文件
docker commit命令
docker commit,实际上是在容器运行起来后,把最上层的“可读写层”,加上原先容器镜像的只读层,打包组成了一个新的镜像;一般包含三个步骤:运行容器;修改容器;将容器保存为新的镜像,过程更加直观。
注:Docker并不建议用户通过这种方式构建镜像,原因是无法对镜像进行审计,存在安全隐患。
实现步骤:使用commit命令构建镜像
❶、运行一个Centos 7 的base镜像;
$ docker run -it centos:7# -it表示以交互的方式运行容器,并进入到容器Unable to find image 'centos:7' locally...: Pull complete
❷、在base镜像中安装软件,并退出;
$ vimbash: vim: command not found #base镜像中没有安装vim工具$ yum install vim -y #使用yum安装vim工具...Complete!$ rpm -qa | grep vim #检查已正常安装vim工具vim-common...$ exit
❸、使用commit命令将修改后的centos7容器封装成一个名为my_iamge的镜像。
$ docker ps -aCONTAINER ID IMAGE ... NAMESff9a563e9fd8 centos:7 ... vibrant_sammet$ docker commit vibrant_sammet my_image#直接将容器commit成名为my_image的镜像$ docker image listREPOSITORY TAG IMAGE ID CREATED SIZEmy_image latest cfdce8636c33 39 seconds ago 366MBcentos 7 8652b9f0cb4c 4 months ago 204MB
❹、验证:可以看到my_image镜像比centos:7镜像多了一层镜像
# docker history指令会显示镜像的分层结构,并且按照镜像层的顺序由上至下排列$ docker history centos:7IMAGE CREATED BY SIZE8652b9f0cb4c /bin/sh -c #(nop) CMD ["/bin/bash"] 0B<missing> /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B<missing> /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4… 204MB$ docker history my_image:latestIMAGE CREATED BY SIZEcfdce8636c33 /bin/bash 162MB8652b9f0cb4c /bin/sh -c #(nop) CMD ["/bin/bash"] 0B<missing> /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B<missing> /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4… 204MB
Dockerfile
dockerfile是一个文本文件,记录着镜像构建的所有步骤,但是底层也是使用docker commit 一层一层构建的,
①、dockerfile构建镜像原理:
Ⅰ. 从base 镜像运行一个容器;
Ⅱ. 执行一条指令,对容器做修改;
Ⅲ. 执行类似docker commit 的操作,生成一个新的镜像层;
Ⅳ. Docker 再基于刚刚提交的镜像运行一个新容器(原容器会被删除);
Ⅴ . 重复 Ⅱ~Ⅳ 步,直到Dockerfile 中的所有指令执行完毕。
②、根据下表字段创建一个dockerfile文件;当使用build指令时,需要指定某个目录为build context,build context 为镜像构建提供所需要的文件或目录;Docker默认会从build context 中查找Dockerfile文件,所以不要将不期望存在的文件放到build context目录下。

③、RUN、CMD、ENTRYPOINT区别
RUN、CMD和ENTRYPOINT都是用来运行命令的,命令执行格式可以分为shell和Exec两种:
shell:直接调用底层的shell(如 /bin/bash)来执行命令,其格式为RUN/CMD/ENTRYPOINT <command>ENV name Cloud #指定环境变量name的值为 CloudENTRYPOINT echo "Hello, $name" #启动容器后输出即为 Hello,Cloud
Exec:直接调用 [command],但是不会被底层的shell解析,其格式为
RUN/CMD/ENTRYPOINT ["executable", "paraml", "param2", ...]ENV name Cloud #指定环境变量name的值为 CloudENTRYPOINT ["/bin/echo","Hello,$name"] #启动容器后输出即为 Hello,$name
❶ RUN:指令通常用于给容器安装应用和软件包;Dockerfile中常常包含多个RUN指令,在当前镜像的顶部执行命令,完成一条RUN指令之后就会创建新的镜像层,也就是说不同的RUN指令代表了不同的镜像层。
❷ CMD:指令允许用户指定容器的默认执行的命令,因为容器是为应用而生的,启动容器最终目的是启动应用(或者是执行一个循环语句)以避免容器启动后无应用而退出(状态为exited),在容器启动且docker run没有指定其他命令时运行
注:如果docker run指定了其他命令,CMD指定的默认命令将被忽略;如果Dockerfile中有多个CMD指令,只有最后一个CMD有效。
❸ ENTRYPOINT:指令可让容器以应用程序或者服务的形式运行,和CMD不同的地方在于ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。
④、实现步骤:使用Dockerfile构建镜像
❶、构建前需要准备build context目录、准备好base镜像
$ pwd #新建一个dockerfile目录作为build context/root/dockerfile$ ls #目录下保存base镜像,dockerfile文件以及需要的file2CentOS-7-x86_64-docker.img.tar.xz dockerfile file2
❷、写一个dockerfile文件
注:可以从一个空镜像开始,也可以从base镜像开始,如果从空镜像开始,必须指定进入到容器之后的shell,如Centos系统是bash shell
[root@node dockerfile]# vim dockerfileFROM scratch #指定从一个空镜像开始MAINTAINER Docker #指定镜像作者ADD CentOS-7-x86_64-docker.img.tar.xz / #添加一个Centos系统的base镜像CMD ["/bin/bash"] #必须指定进入到容器之后使用的shellWORKDIR /testdir #用于指定进入到容器之后的目录(如果没有会创建)RUN touch file1COPY file2 . #将file2拷贝到容器的 /testdir 目录下,注意file2文件必须提前在docker context中存在ENV WELCOME "You are in my container,welcome!"
❸、使用dockerfile创建一个名为first-dockerfile的镜像
#这里的"."表示dockerfile所在的目录,在本实验中是当前目录[root@node dockerfile]# docker build -t first-dockerfile .#docker会将build context中的文件发送给docker daemonSending build context to Docker daemon 36.29MBStep 1/8 : FROM scratch #从一个空镜像开始--->Step 2/8 : MAINTAINER Docker---> Running in b9617c3257dbRemoving intermediate container b9617c3257db---> db4a3a49178d#添加一个Centos系统的base镜像到容器的根目录下Step 3/8 : ADD CentOS-7-x86_64-docker.img.tar.xz /---> 5ed7df8f5c30#指定进入到容器之后使用bash shell(必须指定)Step 4/8 : CMD ["/bin/bash"]---> Running in d5bc15e94c2cRemoving intermediate container d5bc15e94c2c---> 7695f1da0b99Step 5/8 : WORKDIR /testdir #指定进入容器之后的工作目录---> Running in 02bf878461a7Removing intermediate container 02bf878461a7---> 8bee16518a33Step 6/8 : RUN touch file1---> Running in 12b2f04b31edRemoving intermediate container 12b2f04b31ed---> abdb9cbd61c7Step 7/8 : COPY file2 . #这里的"."表示上边指定的工作目录---> 2d32516ab12fStep 8/8 : ENV WELCOME "You are in my container,welcome!"---> Running in 6e6857f7ad1bRemoving intermediate container 6e6857f7ad1b---> 29e652e510d5Successfully built 29e652e510d5Successfully tagged first-dockerfile:latest
❹、验证结果
$ docker run -it first-dockerfilebash-4.2# pwd #查看进入容器之后的工作目录/testdirbash-4.2# ls #查看工作目录下的文件file1 file2bash-4.2# cat file2 #从docker context拷贝过来的file2文件It's worked!bash-4.2# echo $WELCOME #设置的变量值You are in my container,welcome!
3.镜像仓库
镜像命名
可以使用docker tag给镜像重命名,实际上一个特定镜像的名字由两部分组成:repository 和 tag ;tag常用于描述镜像的版本信息,如果执行 docker build 时没有指定 tag, 会使用默认值 latest
镜像命名格式为:[image name] = [repository]: [tag]
注:repository 的完整格式为:[registry-host]: [port]/[namespace]/httpd,只有使用公共Registry且已经登录的时候,registry-host:[port]/[namespace]才可以省略。
搭建本地Registry
①、启动Registry容器,然后将其映射到主机的5000端口,镜像存储位置映射到主机本地目录;
$ docker run -d -p 5000:5000 \ #指定端口映射--restart always #设置开机自启-v /myregistry:/var/lib/registry #指定存储位置映射\registry:2.7...Pull completedb81d506678317bbba6bd028f1ff4e625d4a79aac5007e3c4c4085196f9cfeef
②、修改daemon.json文件中的镜像仓库地址,如果地址是私有仓库的地址,应按照registry-host:[port]的格式修改地址
$ cat /etc/docker/daemon.json{"insecure-registries": ["192.168.1.11:5000"] #本机地址:映射端口号}
③、通过 docker tag 重命名镜像,使之与registry相匹配
#username可以任意但不能包含大写字母$ docker tag httpd:2.4 192.168.1.11:5000/docker/httpd:2.4
④、通过 docker push 上传镜像
$ docker push 192.168.1.11:5000/docker/httpd:2.4The push refers to repository [192.168.1.11:5000/docker/httpd]... Pushed$ pwd/myregistry/docker/registry/v2/repositories$ ls #镜像文件保存在本地指定的用户文件夹里docker
⑤、验证,尝试从私有镜像仓库拉取镜像
注:拉取镜像时必须指定镜像仓库和用户名,不然就会到docker官方网站上去拉取了
#到私有镜像仓库拉取镜像时要补全镜像的tag$ docker pull 192.168.1.11:5000/docker/httpd:2.42.4: Pulling from docker/httpdDigest: sha256:88fb0fb4b406f944e220f082b5a56d1f0e8471abc45fd...Status: Downloaded newer image for 192.168.1.11:5000/docker/httpd:2.4192.168.1.11:5000/docker/httpd:2.4$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZE192.168.1.11:5000/docker/httpd 2.4 683a7aad17d3 2 months ago 138MB
命令补充
docker rmi :用于删除host上的镜像,如果容器正在使用,那么就必须先删除容器,然后才能删除镜像。
docker search:可以无须打开浏览器,在命令行中就可以搜索公共Registry中的镜像,可以根据不同的tag来拉取镜像。
docker save:将某个镜像保存为tar包,命令格式为:
docker save <IMAGE_NAME> -o <TAR_NAME>
docker load:将镜像tar包加载为镜像,格式为:
docker load -i <TAR_NAME>




