Docker(四)—镜像及构建镜像(commit&dockerfile方式)
文章目录 Docker(四)---镜像及构建镜像(commit&dockerfile方式) 1.镜像的分层结构 2.为什么Docker镜像要采用分层结构? 3.镜像的写时复制特性 4.commit构建镜像 5.dockerfile方式 dockerfile常用指令 demo1:COPY拷贝文件 demo2:RUN在容器中运行命令 demo3:镜像分层的缓存特性 demo4:ADD自动解压文件 demo5:ENV定义环境变量 demo6:VOLUME声明数据卷,在封装应用容器时常用 demo7:WORKDIR设置镜像中的当前工作目录 demo8:CMD与ENTRYPOINT demo9:shell和exec格式的区别: 区别1: 区别2:shell格式底层会调用/bin/sh -c来执行命令,可以解析变量,而下面的exec格式不会
1.镜像的分层结构
共享宿主机的kernel base镜像提供的是最小的Linux发行版 同一docker主机支持运行多种Linux发行版采用分层结构的最大好处是: 共享资源 可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
Docker镜像是由文件系统叠加而成。最底端是一个文件引导系统,即bootfs。Docker用户不会与引导文件系统有直接的交互。 Docker镜像的第二层是root文件系统rootfs,通常是一种或多种操作系统,例如ubuntu等。 在Docker中,文件系统永远都是只读的,在每次修改时,都是进行拷贝叠加从而形成最终的文件系统。Docker称这样的文件为镜像。 一个镜像可以迭代在另一个镜像的顶部。位于下方的镜像称之为父镜像,最底层的镜像称之为基础镜像。 最后,当从一个镜像启动容器时,Docker会在最顶层加载一个读写文件系统作为容器。
2.为什么Docker镜像要采用分层结构?共享资源
比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。 这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc是否也会被修改?答案是不会! 修改会被限制在单个容器内。 这就是我们接下来要说的容器 Copy-on-Write 特性。 新数据会直接存放在最上面的容器层。 修改现有数据会先从镜像层将数据复制到容器层,修改后的数据直接 保存在容器层中,镜像层保持不变。 如果多个层中有命名相同的文件,用户只能看到最上面那层中的文件。
base镜像base 镜像简单来说就是不依赖其他任何镜像,完全从0开始建起。 其他镜像都是建立在他的之上,可以比喻为大楼的地基 base 镜像不依赖其他镜像,从 scratch 构建;其他镜像可以之为基础进行扩展。 所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。
3.镜像的写时复制特性Copy- on- Write可写容器层 容器层以下所有镜像层都是只读的 docker从上往下依次查找文件 容器层保存镜像变化的部分,并不会对镜像本身进行任何修改 一个镜像最多127 层
Docker的这种机制我们称之为写时复制。 镜像是用来创建容器的,是容器的只读模板,默认可以从 docker hub 上下载
4.commit构建镜像docker commit构建新镜像三部曲:
缺点:
效率低、可重复性弱、容易出错
使用者无法对镜像进行审计,存在安全隐患
docker ps
将容器保存为新的镜像:
docker commit 3be43c7fa968 busybox:v1
查看指定镜像的创建历史:
docker history busybox:latest docker history busybox:v1
发现v1中使用了原来的busybox的两层,并且在上面新建了一层,叠加在上面
docker ps docker rm -f 3be43c7fa968 #删除正在运行的容器需要-f参数强制删除 docker run -it --rm busybox:v1
可以看到即使删除了busybox容器,但修改的镜像被构建成了一个新的镜像,这种commit的方式将数据提交到了镜像层里,没有把数据落到宿主机的磁盘上
5.dockerfile方式 dockerfile常用指令FROM:指定base镜像,如果本地不存在会从远程仓库下载。 MAINTAINER:设置镜像的作者,比如用户邮箱等。 COPY:把文件从build context复制到镜像 支持两种形式: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 server1. 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,如果指定了很多,只有最后一个有效。
demo1:COPY拷贝文件COPY:把文件从build context复制到镜像 支持两种形式:COPY src dest 和 COPY [ "src" , "dest" ] src必须指定build context中的文件或目录
step1 创建一个Dockerfile:
mkdir docker cd docker/ vim Dockerfile FROM busybox #指定开始的镜像,若镜像不存在,会去拉取该镜像 COPY testfile / #把当前目录下的testfile文件拷贝到容器的根目录下
注意: 1.dockerfile的目录不要再根目录下,因为默认创建时会把当前目录的所有数据发送给docker引擎,如果在根目录下,就会把根下的所有数据发送给docker进行构建,这显然时不合理的 2.COPY参数要求要拷贝的文件必须在当前目录,不能写绝对路径,只能是相对路径。拷贝的目的地可以是目录或文件
echo redhat > testfile
step2 删除之前的镜像(没有做过之前的实验可忽略此步骤):
docker images docker rmi busybox:v1 docker rmi busybox:v2 docker images
step3 构建镜像:
docker build -t demo:v1 .
可以看到在dockerfile里执行一行命令,提交一次。通过dockerfile可以清晰的看到在每一层里干了什么,可以实现安全审计功能;而commit方式构建镜像时是看不到的
docker history demo:v1
demo2:RUN在容器中运行命令RUN:在容器中运行命令并创建新的镜像层,常用于安装软件包: RUN yum install - y vim
step1 修改Dockerfile:
step1 修改Dockerfile: vim Dockerfile FROM busybox COPY testfile / RUN echo hello nigar !! > file1
说明:文件第一行内容是把镜像运行成一个容器,后面所有操作都是在容器中完成的
step2 构建镜像:
docker build -t demo:v2 .
v2是在v1的基础上又构建了一层,下面的层没有改变,直接共享了。我们可以看到COPY testfile /的动作没有重复再做,而是Using cache,即使用缓存 :step3 测试:
docker run -it demo:v2
查看到file1的内容即为hello nigar !!
demo3:镜像分层的缓存特性step1 修改Dockerfile:
vim Dockerfile FROM busybox COPY testfile / RUN echo hello nigar !! > file1 RUN echo hello nigar !! > file2
step2 构建镜像:
docker build -t demo:v3 .
Dockerfile没有变更时会使用本地的cache,无需重新做,可以加速构建过程。这就是镜像分层的缓存特性
step3 测试:
docker run -it demo:v3
demo4:ADD自动解压文件ADD:用法与COPY类似,不同的是src可以是归档压缩文件,文件会被自动解压到dest,也可以自动下载URL并拷贝到镜像: ADD html. tar / var/ www ADD http: / / ip/ html. tar / var/ www
step1 放一个nginx的压缩包在/root/docker下 step2 修改Dockerfile:
vim Dockerfile FROM busybox ADD nginx-1.16.1.tar.gz /
step3 构建镜像:
docker build -t demo:v4 .
ADD的用法与COPY类似,但不同的是它可以将文件自动解压到dest
step4 测试:
docker run -it demo:v4
demo5:ENV定义环境变量ENV:设置环境变量,变量可以被后续的指令使用: ENV HOSTNAME sevrer1. example. com
step1 修改Dockerfile:
vim Dockerfile FROM busybox ENV hostname server1 ADD nginx-1.16.1.tar.gz / #定义环境变量hostname为server1
step2 构建镜像:
docker build -t demo:v5 .
step3 测试:
docker run -it demo:v5
env查看到环境变量信息:
demo6:VOLUME声明数据卷,在封装应用容器时常用VOLUME:申明数据卷,通常指定的是应用的数据挂载点: VOLUME [ "/var/www/html" ]
step1 修改Dockerfile:
vim Dockerfile FROM busybox ENV hostname server1 ADD nginx-1.16.1.tar.gz / VOLUME ["/data"]
step2 构建镜像:
docker build -t demo:v6 .
step3 查看镜像的创建历史:
docker history demo:v6
step4 进入容器,创建文件:
docker run -it demo:v6 按ctrl+p+q退出
step5 查看demov6的挂载信息:
docker ps #找出对应的容器ID docker inspect f9770256d7bc
docker引擎在启动熔器时发现定义了卷,会自动生成一个卷。而docker引擎在启动容器时,自动在本地为它创建了这个目录,并且挂载在容器内,可以让容器读取到本地的数据目录
step6 进入目录,查看到刚刚创建的文件file:
cd /var/lib/docker/volumes/2abf295690bffe9eb6caac67aa365a666d49a8ed1f42c108f7b754ac2af7f260/_data
step7 释放数据卷:
docker ps docker rm -f f9 #删除容器(此处使用容器ID的简写,因为只有一个f9开头的ID) docker volume prune #释放数据卷 docker volume ls #查看有哪些数据卷
demo7:WORKDIR设置镜像中的当前工作目录WORKDIR:为RUN、CMD、ENTRYPOINT、ADD和COPY指令设置镜像中的当前工作目录,如果目录不存在会自动创建。
step1 修改Dockerfile:
vim Dockerfile FROM busybox ENV hostname server1 WORKDIR /nginx ADD nginx-1.16.1.tar.gz /nginx VOLUME ["/data"]
step2 构建镜像:
docker build -t demo:v7 .
step3 测试:
docker run -it --rm demo:v7
我们可以发现,进入容器时就默认在/nginx这个目录下,而将nginx的包解压在了这个目录中。 这个目录之前并不存在,是WORKDIR自动创建的
demo8:CMD与ENTRYPOINT这两个指令都是用于设置容器启动后执行的命令,但CMD会被docker run后面的命令行覆盖,而ENTRYPOINT不会被忽略,一定会被执行。 docker run后面的参数可以传递给ENTRYPOINT指令当作参数。 Dockerfile中只能指定一个ENTRYPOINT,如果指定了很多,只有最后一个有效。
CMD:step1 修改Dockerfile:
vim Dockerfile FROM busybox ENV hostname server1 WORKDIR /nginx ADD nginx-1.16.1.tar.gz /nginx VOLUME ["/data"] CMD echo 'hello docker!!!'
step2 构建镜像:
docker build -t demo:v8 .
step3 测试:
docker run -it --rm demo:v8 docker run -it --rm demo:v8 sh #命令被覆盖
ENTRYPOINT:step1 修改Dockerfile:
vim Dockerfile FROM busybox ENV hostname server1 WORKDIR /nginx ADD nginx-1.16.1.tar.gz /nginx VOLUME ["/data"] ENTRYPOINT echo 'hello docker!!!'
step2 构建镜像:
docker build -t demo:v9 .
step3 测试:
docker run -it --rm demo:v9 docker run -it --rm demo:v9 sh #命令没有被覆盖
demo9:shell和exec格式的区别: 区别1:exec格式:step1 修改Dockerfile:
vim Dockerfile FROM busybox ENV hostname server1 WORKDIR /nginx ADD nginx-1.16.1.tar.gz /nginx VOLUME ["/data"] ENTRYPOINT ["/bin/echo","hello"] CMD ["docker!!"]
step2 构建镜像:
docker build -t demo:v10 .
step3 测试:
docker run -it --rm demo:v10 docker run -it --rm demo:v10 linux #输出的docker变为linux
说明:Exec格式时,ENTRYPOINT可以通过CMD提供额外参数,CMD的额外参数可以在容器启动时动态替换。在shell格式时,ENTRYPOINT会忽略任何CMD或者docker run提供的参数
区别2:shell格式底层会调用/bin/sh -c来执行命令,可以解析变量,而下面的exec格式不会shell格式: step1 修改Dockerfile:
vim Dockerfile FROM busybox ENV name redhat ENTRYPOINT echo "hello,$name"
step2 构建镜像:
docker build -t demo:v11 .
step3 测试:
docker run -it --rm demo:v11
可以看到变量$name
的值被输出了
exec格式: step1 修改Dockerfile:
vim Dockerfile FROM busybox ENV name redhat ENTRYPOINT ["/bin/echo","hello,$name"]
step2 构建镜像:
docker build -t demo:v12 .
step3 测试:
docker run -it --rm demo:v12
可以看到变量$name
没有被解析,而是直接输出了$name
所以exec格式需要改写:
vim Dockerfile FROM busybox ENV name redhat ENTRYPOINT ["/bin/sh","-c","echo hello,$name"]
再次测试,变量就被解析了