Docker commit和Dockerfile构建镜像
1 镜像的分层结构
Docker 支持通过扩展现有镜像,创建新的镜像。新镜像是从 base 镜像一层一层叠加生成的,Dockerfile执行完每一条指令都会创建一个新的镜像,最终的镜像由层层叠加而成。
分层的好处是共享资源: 当多个镜像从相同的base镜像构建而来,那么Docker host只需要在磁盘保存一份base镜像,同时内存中也只需要加载一份base镜像,就可以为所有的容器服务了。
容器的修改并不会影响镜像: 当容器启动时,一个新的可写层被加载到镜像的顶层,这一层通常被成为“容器层”。对容器的改动,无论添加、删除、还是修改文件都只会发生在容器层中。容器层是可写的,镜像层是只读的。
2 镜像的构建
2.1 Docker commit 构建镜像
docker commit 构建新镜像三部曲
(1)运行容器:docker run -it --name demo busybox
(2)修改容器
(3)将容器保存为新的镜像:docker commit -m "add files" demo demo:v1
[root@server1 sysctl.d]# docker run -it --name demo busybox ##运行容器
/ # touch file1 ##修改容器 (以下命令在容器内运行)
/ # ls
bin dev etc file1 home proc root sys tmp usr var
/ #
[root@server1 sysctl.d]# docker commit -m "add files" demo demo:v1 ##将容器保存为新的镜像
sha256:02628d846be25a335e445eb4dfbf61f964e0337d3f2a98010307504f7f6d1c1a
[root@server1 sysctl.d]# docker images ##查看镜像
REPOSITORY TAG IMAGE ID CREATED SIZE
demo v1 02628d846be2 9 seconds ago 1.23MB
busybox latest b97242f89c8a 11 days ago 1.23MB
- 查看镜像的分层结构
[root@server1 sysctl.d]# docker history demo:v1 ##查看镜像的分层结构
IMAGE CREATED CREATED BY SIZE COMMENT
02628d846be2 23 seconds ago sh 15B add files
b97242f89c8a 11 days ago /bin/sh -c #(nop) CMD ["sh"] 0B
<missing> 11 days ago /bin/sh -c #(nop) ADD file:92e389f575fd4d0a4… 1.23MB
镜像的缓存特性
- 删除容器demo:
docker rm demo
[root@server1 sysctl.d]# docker rm demo
demo
[root@server1 sysctl.d]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- 使用镜像
demo:v1
启动一个名为demo的容器:docker run -it --name demo demo:v1
,可以看到对demo镜像的修改仍然有效(file1存在),查看demo:v1的历史信息无法了解镜像的具体信息,无法对镜像进行审计,存在安全隐患
[root@server1 sysctl.d]# docker run -it --name demo demo:v1 ##镜像的缓存特性
/ # ls
bin dev etc file1 home proc root sys tmp usr var
/ #
[root@server1 sysctl.d]# docker history demo:v1
IMAGE CREATED CREATED BY SIZE COMMENT
02628d846be2 2 minutes ago sh 15B add files
b97242f89c8a 11 days ago /bin/sh -c #(nop) CMD ["sh"] 0B
- 查看 nginx:latest 镜像的历史信息:
docker history nginx:latest
[root@server1 sysctl.d]# docker history busybox:latest ##查看镜像的分层结构
IMAGE CREATED CREATED BY SIZE COMMENT
b97242f89c8a 11 days ago /bin/sh -c #(nop) CMD ["sh"] 0B
<missing> 11 days ago /bin/sh -c #(nop) ADD file:92e389f575fd4d0a4… 1.23MB
[root@server1 sysctl.d]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
demo v1 02628d846be2 3 minutes ago 1.23MB
busybox latest b97242f89c8a 11 days ago 1.23MB
[root@server1 sysctl.d]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7cc55d083c5d demo:v1 "sh" About a minute ago Exited (0) About a minute ago demo
[root@server1 sysctl.d]# docker rm demo
demo
[root@server1 sysctl.d]# docker rmi demo:v1
Untagged: demo:v1
Deleted: sha256:02628d846be25a335e445eb4dfbf61f964e0337d3f2a98010307504f7f6d1c1a
Deleted: sha256:17e2efccb26766262c69378b558644f1723023262fecbf36b3eb6a54cb0a4b96
2.2 Dockerfile构建镜像
Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取Dockerfile中的指令自动生成映像
Docker以从上到下的顺序运行Dockerfile的指令。为了指定基本映像,第一条指令必须是FROM
-
创建一个Dockerfile
mkdir /docker cd /docker vim Dockerfile
FROM busybox ##指定base镜像,如果本地不存在会从远程仓库下载
RUN touch file3
- 构建镜像:
docker build -t demo:v1 .
[root@server1 docker]# docker build -t demo:v1 .
Sending build context to Docker daemon 2.048kB
Step 1/2 : from busybox
---> b97242f89c8a
Step 2/2 : RUN touch file2
---> Running in 17ab5814c008
Removing intermediate container 17ab5814c008
---> 511ef075d432
Successfully built 511ef075d432
Successfully tagged demo:v1
- 查看镜像的创建的历史过程:
docker history demo:v1
[root@server1 docker]# docker history demo:v1
IMAGE CREATED CREATED BY SIZE COMMENT
511ef075d432 34 seconds ago /bin/sh -c touch file2 0B
b97242f89c8a 11 days ago /bin/sh -c #(nop) CMD ["sh"] 0B
<missing> 11 days ago /bin/sh -c #(nop) ADD file:92e389f575fd4d0a4… 1.23MB
- 修改Dockerfile文件:
vim /docker/Dockerfile
FROM demo:v1
RUN touch file3
- 构建镜像:
docker build -t demo:v2 .
[root@server1 docker]# docker build -t demo:v2 .
Sending build context to Docker daemon 2.048kB
Step 1/2 : from demo:v1
---> 511ef075d432
Step 2/2 : RUN touch file3
---> Running in 87737d7a8e23
Removing intermediate container 87737d7a8e23
---> 97114c94a571
Successfully built 97114c94a571
Successfully tagged demo:v2
[root@server1 docker]# docker history demo:v2 ##查看镜像的历史创建过程
IMAGE CREATED CREATED BY SIZE COMMENT
97114c94a571 7 seconds ago /bin/sh -c touch file3 0B
511ef075d432 12 minutes ago /bin/sh -c touch file2 0B
b97242f89c8a 11 days ago /bin/sh -c #(nop) CMD ["sh"] 0B
<missing> 11 days ago /bin/sh -c #(nop) ADD file:92e389f575fd4d0a4… 1.23MB
3 Dockerfile常用的命令
(1)FROM:指定基础镜像,必须为第一个命令
(2)MAINTAINER: 维护者信息
(3)RUN:构建镜像时执行的命令
(4)COPY:把文件从build context复制到镜像
- 支持两种形式:
COPY src dest
和COPY ["src", "dest"]
- src必须指定build context中的文件或目录
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM busybox
RUN touch file3 ##创建file3
COPY index.html / ##复制当前目录index.html文件到镜像的根目录
-
构建镜像demo:v1:`docker build -t demo:v1 .
-
查看镜像的创建的历史过程:
docker history demo:v1
-
运行容器:
docker run -it --rm demo:v1
(5)ADD:用法与COPY类似,不同的是src如果是归档压缩文件,文件会被自动解压到dest(网络压缩资源不会被解压),也可以自动下载URL并拷贝到镜像
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM busybox
RUN touch file3
COPY index.html /
ADD test.tar.gz /mnt
- 构建镜像demo:v2:
docker build -t demo:v2 .
- 查看镜像的创建的历史过程:
docker history demo:v2
- 运行容器:
docker run --rm -it demo:v2
,test.tar.gz文件被自动解压到/mnt
(6)ENV:设置环境变量,变量可以被后续的指令使用
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM busybox
RUN touch file3
COPY index.html /
ADD test.tar.gz /mnt
ENV HOSTNAME=server1
- 构建镜像demo:v3:
docker build -t demo:v3 .
- 查看镜像创建的历史过程:
docker history demo:v3
- 运行容器:
docker run --rm -it demo:v3
(7)EXPOSE:指定于外界交互的端口
如果容器中运行应用服务,可以把服务端口暴露出去, EXPOSE并不会让容器的端口访问到主机;要使其可访问,需要在docker run运行容器时通过-p来发布这些端口
- 从仓库中下载nginx镜像:
docker pull nginx
- 查看所有的镜像资源:
docker images
不做端口映射,会随机分配端口号
- 将nginx打入后台运行:
docker run -d --name demo nginx
- 查看服务和端口号:
netstat -antlp
- 查看容器的配置信息:
docker inspect demo
随机指定端口号
-
将镜像nginx以后台模式启动一个容器,并将容器的80端口映射到主机随机端口:
docker run -d --name demo -P nginx
-
查看容器的配置信息:
docker inspect demo
- 查看服务和端口号:
netstat -antlp
(8)VOLUME:用于指定持久化目录
一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
1- 卷可以容器间共享和重用
2- 容器并不一定要和其它容器共享卷
3- 修改卷后会立即生效
4- 对卷的修改不会对镜像产生影响
5- 卷会一直存在,直到没有任何容器在使用它
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM busybox
RUN touch file3
COPY index.html /
ADD test.tar.gz /mnt
ENV HOSTNAME=server1
EXPOSE 80
VOLUME ["/data"]
- 构建镜像demo:v5:
docker build -t demo:v5 .
- 查看镜像的创建的历史过程:
docker history demo:v5
- 运行容器:
docker run -it --name demo demo:v5
- 查看容器的配置信息:
docker inspect demo
- 进入卷的挂载资源目录,在该目录中创建file2
- 进入demo的容器
docker attach demo
,/data目录中也有file2
- 列出本地数据卷:
docker volume ls
- 移除未使用的数据卷:
docker volume prune
(9)CMD 与 ENTRYPOINT:都是用于设置容器启动后执行的命令,但CMD会被docker run后面的命令行覆盖,而ENTRYPOINT不会被忽略,一定会被执行。
- CMD会被docker run后面的命令行覆盖,nginx不会执行,bash会执行:
docker run -it --rm nginx bash
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM busybox
RUN touch file3
COPY index.html /
ADD test.tar.gz /mnt
ENV HOSTNAME=server1
EXPOSE 80
VOLUME ["/data"]
CMD echo "hello $HOSTNAME"
- 构建镜像demo:v6:
docker build -t demo:v6 .
- 查看镜像的创建的历史过程:
docker history demo:v6
- 运行容器:
docker run -it --name demo demo:v6
Shell格式底层会调用/bin/sh -c来执行命令,可以解析变量,exec格式不会,但所exec格式修改后可以解析变量
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM busybox
RUN touch file3
COPY index.html /
ADD test.tar.gz /mnt
ENV HOSTNAME=server1
EXPOSE 80
VOLUME ["/data"]
CMD ["/bin/sh", "-c", "echo hello $HOSTNAME"]
-
构建镜像demo:v7:
docker build -t demo:v7 .
-
运行容器:
docker run -it --name demo demo:v7
docker run后面的参数可以传递给ENTRYPOINT指令当作参数Dockerfile中只能指定一个ENTRYPOINT,如果指定了很多,只有最后一个有效
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM busybox
RUN touch file3
COPY index.html /
ADD test.tar.gz /mnt
ENV HOSTNAME=server1
EXPOSE 80
VOLUME ["/data"]
ENTRYPOINT ["echo", "hello"]
ENTRYPOINT ["echo", "world"]
- 构建镜像demo:v8:
docker build -t demo:v8 .
- 运行容器:
docker run -it --name demo demo:v78
4 使用Dockerfile构建nginx镜像
- 下载nginx的源码 nginx-1.18.0.tar.gz
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM rhel7
COPY dvd.repo /etc/yum.repos.d/
ADD nginx-1.18.0.tar.gz /mnt
WORKDIR /mnt/nginx-1.18.0
RUN rpmdb --rebuilddb
RUN yum install -y gcc pcre-devel zlib-devel make
RUN ./configure
RUN make
RUN make install
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
- 构建镜像webserver:v1:
docker build -t webserver:v1 .
- 查看镜像:
docker images
- 显示镜像的创建过程:
docker history webserver:v1
- 运行容器:
docker run -d --name webserver webserver:v1
- 列出所有容器:
docker ps
- 查看容器的配置信息:
docker inspect webserver
- 测试
5 镜像的优化
镜像优化的方法:
- 选择最精简的基础镜像
- 减少镜像的层数
- 清理镜像构建的中间产物
- 优化网络请求
- 尽量用构建缓存
- 使用多阶段构建镜像
(1)减少镜像的层数 、用构建缓存
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM rhel7
COPY dvd.repo /etc/yum.repos.d/
ADD nginx-1.18.0.tar.gz /mnt
WORKDIR /mnt/nginx-1.18.0
RUN rpmdb --rebuilddb && yum install -y gcc pcre-devel zlib-devel make &>/dev/null && ./configure &>/dev/null && make &>/dev/null && make install &>/dev/null
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
- 构建镜像webserver:v2:
docker build -t webserver:v2 .
- 列出本地所有的镜像:
docker images
- 显示镜像的创建过程:
docker history webserver:v2
(2)清理镜像构建的中间产物
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM rhel7
COPY dvd.repo /etc/yum.repos.d/
ADD nginx-1.18.0.tar.gz /mnt
WORKDIR /mnt/nginx-1.18.0
RUN rpmdb --rebuilddb && yum install -y gcc pcre-devel zlib-devel make &>/dev/null && ./configure &>/dev/null && make &>/dev/null && make install &>/dev/null && yum remove -y gcc make &>/dev/null && yum clean all &>/dev/null
EXPOSE 80
VOLUME ["/usr/local/nginx/html"]
CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]
- 构建镜像webserver:v3:
docker build -t webserver:v3 .
- 列出本地所有的镜像:
docker images
- 显示镜像的创建过程:
docker history webserver:v3
(3)多阶段构建镜像
通过多阶段构建,最终镜像可以仅包含最后生成的可执行文件,和必须的运行时依赖,大大减少镜像体积。
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM rhel7 as build
COPY dvd.repo /etc/yum.repos.d/
ADD nginx-1.18.0.tar.gz /mnt
WORKDIR /mnt/nginx-1.18.0
RUN rpmdb --rebuilddb && yum install -y gcc pcre-devel zlib-devel make && ./configure && make && make install && yum remove -y gcc make && yum clean all
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;"]
- 构建镜像webserver:v4: docker build -t webserver:v4 .
- 列出本地所有的镜像:
docker images
- 显示镜像的创建过程:
docker history webserver:v4
(4)选择最精简的基础镜像
- 从仓库中下载nginx1.18.0镜像:
docker pull nginx:1.18.0
- 下载基础镜像,Distroless是Google推出的一个仅仅包含运行时环境,不包含包管理器,shell等其他程序
导入镜像到本地:docker load -i base-debian10.tar
- 编辑Dockerfile文件:
vim /docker/Dockerfile
FROM nginx:1.18.0 as base
# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
ARG TIME_ZONE
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/ld-* /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* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libdl* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libpthread* /opt && \
cp -a --parents /lib/x86_64-linux-gnu/libcrypt* /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_ZONE:-ROC} /opt/etc/localtime
FROM gcr.io/distroless/base-debian10
COPY --from=base /opt / ### 从base中复制编译好的二进制文件
EXPOSE 80 443
ENTRYPOINT ["nginx", "-g", "daemon off;"]
- 构建镜像webserver:v5:
docker build -t webserver:v5 .
- 显示镜像的创建过程:
docker history webserver:v5
- 查看webserver的镜像:
docker images webserver
-
运行容器:
docker run -d --name webserver webserver:v5
-
查看容器的配置信息:
docker inspect webserver
- 测试