Docker(四)—镜像及构建镜像(commit&dockerfile方式)
文章目录
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:
<font color=dahoc>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"]
再次测试,变量就被解析了