Dockerfile:镜像构建
Docker File
Docker 镜像原理
思考:
- Docker 镜像本质是什么?
- Docker 中一个 centos 镜像为什么只有 200MB,而一个 centos 操作系统的 iso 文件要几个 G ?
- Docker 中一个 tomcat 镜像为什么有 500MB,而一个 tomcat 安装包只有 70 多 MB ?
操作系统组成:
- 进程调度子系统
- 进程通信子系统
- 内存管理子系统
- 设备管理子系统
- 文件管理子系统
- 网络通信子系统
- 作业控制子系统
Linux 文件系统由 bootfs 和 rootfs 两部分组成:
- bootfs:包含 bootloader(引导加载程序)和 kernel(内核)。
- rootfs:root 文件系统,包含的就是典型 Linux 系统中的 /dev,/proc,/bin,/etc 等标准目录和文件。
- 不同的 linux 发行版,其 bootfs 基本一样,而 rootfs 则不同,如 ubuntu,centos 等。
Docker 镜像:
- Docker 镜像是由特殊的文件系统叠加而成。
- 最底端是 bootfs,其使用的是宿主机的 bootfs 。
- 第二层是 root 文件系统rootfs,称为 base image 。
- 再往上可以叠加其他的镜像文件。
- 统一文件系统(Union File System)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。
- 一个镜像可以放在另一个镜像的上面。位于下面的镜像称为父镜像,最底部的镜像成为基础镜像。
- 当从一个镜像启动容器时,Docker 会在最顶层加载一个读写文件系统作为容器。
总结:
- Docker 镜像本质是什么?
- 是一个分层文件系统。
- Docker 中一个 centos 镜像为什么只有 200MB,而一个 centos 操作系统的 iso 文件要几个 G ?
- Centos 的 iso 镜像文件包含 bootfs 和 rootfs,而 docker 的 centos 镜像复用操作系统的 bootfs,只有 rootfs 和其他镜像层。
- Docker 中一个 tomcat 镜像为什么有 500MB,而一个 tomcat 安装包只有 70 多 MB ?
- 由于 docker 中镜像是分层的,tomcat 虽然只有 70 多 MB,但他需要依赖于父镜像和基础镜像,所有整个对外暴露的 tomcat 镜像大小 500 多 MB 。
Dockerfile
镜像制作
Docker 镜像如何制作?
- 方法一:容器转为镜像
docker commit 容器id 镜像名称:版本号
docker save -o 压缩文件名称 镜像名称:版本号
docker load –i 压缩文件名称
- 方法二:使用 Dockerfile
Dockerfile 概念
什么是 Dockerfile ?
- Dockerfile 是一个文本文件,包含了一条条的指令。
- 每一条指令构建一层,基于基础镜像,最终构建出一个新的镜像。
- 对于开发人员:可以为开发团队提供一个完全一致的开发环境。
- 对于测试人员:可以直接拿开发时所构建的镜像或者通过 Dockerfile 文件构建一个新的镜像开始工作。
- 对于运维人员:在部署时,可以实现应用的无缝移植。
- 官方 Dockerfile 文件参考:https://github.com/CentOS/CentOS-Dockerfiles
Dockerfile 主要组成部分:
- 基础镜像信息:如 FROM centos:6.8
- 制作镜像时的操作命令:如 RUN yum insatll openssh-server -y
- 容器启动时的执行命令:如 CMD ["/bin/bash"]
Dockerfile 常用命令
- FROM:指定基础镜像。
- MAINTAINER:指定维护者信息。
- RUN:制作镜像过程中的操作命令。
- ADD:加载主机文件(会自动解压)。
- WORKDIR:设置当前工作目录。
- VOLUME:设置卷,挂载并映射主机目录。
- EXPOSE:指定对外的端口。
- CMD:指定容器启动后的要执行的命令。
- COPY:用于将本地文件或者目录拷贝进容器镜像。
- ENV :环境变量。
- ENTRYPOINT:容器启动后执行的命令。
FROM 命令
使用 Dockerfile 做定制的镜像,都是从基础镜像开始的。这个基础镜像可以是各种版本的操作系统、或带着中间件的操作系统、或是自定义的操作系统镜像。本地有就从本地直接用,本地没有会去下载。
好习惯是将 Dockerfile 文件放在集中的位置,最好是共享存储里面。然后以项目名称创建目录,以 tag 名创建子目录。
[root@localhost ~]# mkdir dockerfile
[root@localhost ~]# cd dockerfile/
[root@localhost dockerfile]# mkdir nginx
[root@localhost dockerfile]# cd nginx
[root@localhost nginx]# mkdir v1from
[root@localhost nginx]# cd v1from/
[root@localhost v1from]# vi Dockerfile
注意创建 Dockerfile 名称就只能叫做“Dockerfile”,不然的话需要用 -f 参数指定 Dockerfile,看个人习惯。如果使用清晰的目录结构,建议使用 Dockerfile 命名。
这个 Dockerfile 只有一行 from 命令,即引用 nginx 这个基础镜像,剩余的动作完全没有。这并没有实际意义,除了演示不会有人这么做 Dockerfile 的。
# 构建镜像
docker build -t nginx:v1from .
注意:
- 最后的“.”,代表从本目录的 Dockerfile 执行,也可以使用绝对路径。
- 打镜像的时候最后镜像名称和t ag 都由自己规划,即“:”前后都是自定义。
- 如果名称和 tag 和之前版本重复,相当于提交新版本并覆盖原有镜像。
使用新做的 nginx:v1from 镜像启动容器:
RUN 命令
用于执行后面跟着的命令行命令。有两种格式:
RUN <命令行命令>
RUN ["可执行文件", "参数1", "参数2", ...]
仍然以 nginx 为基础镜像,这次增加一个 RUN 命令:
说明:可以到 docker.com 上查看 nginx 镜像的说明文档,找到这个镜像中 nginx 默认 web 目录的位置为:/usr/share/nginx/html/,并使用 echo 命令打印一句话并重定向到该目录下的 index.html 文件中。
容器启动后,再通过 nginx 看到这个静态页面查看命令执行结果。
可见默认文件 index.html 的内容已被修改。
注意事项:
- 命令行命令就是 shell 命令,常用于此的命令包括:yum install/apt-get、wget、tar 等,当然也可以用下一节学习的 COPY 命令,拷贝脚本到容器中,然后用 RUN 执行。
- RUN 命令仅在 docker build 过程中生效,镜像完成后,docker run 后 RUN 脚本或命令则不再生效(可以用 yum 命令理解,已经在 docker build 的时候安装相应的东西进入镜像,启动容器的时候已经用到 yum 安装的内容,自然不用再安装一遍)。
- RUN 是会触发镜像分层的,不合理的 RUN 会增加镜像大小。看以下四个例子:
# docker build -t nginx:3runwithoutrm .
FROM centos
RUN yum install -y wget
RUN wget http://nginx.org/download/nginx-1.19.10.tar.gz
RUN tar -zxvf nginx-1.19.10.tar.gz
# docker build -t nginx:1runwithoutrm .
FROM centos
RUN yum install wget && \
wget http://nginx.org/download/nginx-1.19.10.tar.gz && \
tar -zxvf nginx-1.19.10.tar.gz
# docker build -t nginx:4runwithrm .
FROM centos
RUN yum install wget
RUN wget http://nginx.org/download/nginx-1.19.10.tar.gz
RUN tar -zxvf nginx-1.19.10.tar.gz
RUN rm -f nginx-1.19.10.tar.gz
# docker build -t nginx:1runwithrm .
FROM centos
RUN yum install wget && \
wget http://nginx.org/download/nginx-1.19.10.tar.gz && \
tar -zxvf nginx-1.19.10.tar.gz && \
rm -f nginx-1.19.10.tar.gz
结论:
- RUN 下载的文件,在同一个 RUN 中进行了删除,才能减小镜像大小(写时复制和联合挂载的特性)。
- RUN 的个数并不是越少越好,细分后的 RUN 更容易在构建时候被当做 cache 利用到,一个 RUN 里过多的 && 连接命令则很难被复用。
COPY 命令
COPY 命令用于将本地文件或者目录拷贝进容器镜像。
如下图,构建 dockerfile,目录 web,web 下有一个 a.html 文件,文件中一句话。
启动容器并从浏览器中查看目录下的文件:
注意事项:
- COPY 命令在拷贝目录的时候,和 cp 的习惯不一致,会把目录下面的内容拷走,而不拷目录本身,COPY a /path/to/ 相当于 cp a/* /path/to/。
- COPY 命令在拷贝的时候,如果容器中最后一级目录不存在,会新建该目录并拷贝。综合上边一条,如果想要正确的把目录 a 拷贝到 /path/to/ 下,正确的写法是 COPY a /path/to/a(此时 /path/to 目录存在,/path/to/a 不存在)。
CMD 命令
CMD 用于容器启动时后执行命令,注意和 RUN 的区别(RUN 用于构建的时候执行命令)。
在没有 ENTRYPOINT 的情况下,CMD 就是容器用来启动前台用户进程的命令。
格式:
CMD <shell 命令>
CMD ["可执行文件或命令", "参数1", "参数2", …]
这里推荐第二种写法,不然的话第一种可执行文件只能用 sh 脚本。
Dockerfile 说明:
- yum 安装的 nginx,默认页面存放路径依然是 /usr/share/nginx/html;
- 该例子首先拷贝一个静态页面 default.html 到默认路径中;
- 然后新建一个静态页面路径,将 modify.html 文件拷贝到新建的路径中;
- COPY 一份修改后的配置文件到 /nginx 下;
- 最后用 CMD 启动 nginx,注意 -g daemon off; 是容器启动必须加的参数(别漏写“;”),以避免 nginx 后台启动,否则 nginx 后台启动将导致容器直接退出。
1)docker run 时不加启动参数
注意,此处 Dockerfile 中未加入 EXPOSE,需要指定后端端口,用小写 -p 启动:
docker run -d -p 80:80 nginx:v4cmd
2)docker run 时加启动参数
docker run -d -p 81:80 nginx:v4cmd nginx -g "daemon off;" -c /nginx/nginx.conf
可以看到,若启动时带了运行参数,则会覆盖掉 Dockerfile 中的 CMD 执行内容,以新的命令和参数启动。
CMD 注意事项:
- CMD 如果有多个,只有最后一个生效。
- CMD 会被传入的启动参数覆盖,这个特性在测试时很有用。
ENTRYPOINT 命令
和 CMD 是同类命令,可以单独使用,也可以和 CMD 配合使用。
单独使用 ENTYRPOINT 格式:
ENTYRPOINT ["命令或者可执行脚本", "参数一", "参数二", …]
单独使用 ENTRYPOINT 时,和 CMD 不同,其参数不会被 docker run 时传入的启动参数所覆盖,但是 docker run 传入的启动参数会作为 ENTRYPOINT 指定命令的参数传入。
Dockerfile 示例内容:
FROM centos
RUN yum install -y nginx
COPY default.html /usr/share/nginx/html
RUN mkdir -p /nginx/html
COPY modify.html /nginx/html
COPY nginx.conf /nginx
ENTRYPOINT ["nginx","-g","daemon off;"]
1)docker run 时不加启动参数
2)docker run 时加启动参数
docker run -d -p 81:80 nginx:v5entrypoint -g "daemon off;" -c /nginx/nginx.conf
可以看到,modify.html 生效,但是这次传入的参数并不是完整的 nginx -g "daemon off;" -c /nginx/nginx.conf 命令,而是仅仅只有参数,并且参数覆盖了镜像中 ENTRYPOINT 的参数。
ENTRYPOINT 和 CMD 混合使用
若两个命令同时存在,CMD 本身不再运行命令,其内容变成 ENTRYPOINT 的参数,同时 CMD 的内容依然可以被 docker run 时候的启动参数覆盖,进而传递给 ENTRYPOINT。
换句话说,两者组合,可以完成定参和变参的混合定义。
Dockerfile 示例:
FROM centos
RUN yum install -y nginx
COPY default.html /usr/share/nginx/html
RUN mkdir -p /nginx/html
COPY modify.html /nginx/html
COPY nginx.conf /nginx
ENTRYPOINT ["nginx","-g","daemon off;","-c"]
CMD ["/etc/nginx/nginx.conf"]
1)docker run 时不带参数
2)docker run 时带参数
实际使用中,ENTRYPOINT 和 CMD 组合的方式会更常见一些。
VOLUME 命令
实现效果和 docker run -v 挂载主机目录一样。
写在 Dockerfile 中的好处是,万一在 docker run 时候忘记了 -v 参数,数据也不会丢失。
另外 docker run -v 指令会覆盖 Dockerfile 的 VOLUME 指令。
案例一:自定义 centos7 镜像
需求:自定义 centos7 镜像
- 默认登录路径为 /usr
- 可以使用 vim
实现步骤:
-
文件内容:
- 定义父镜像:FROM centos:7
- 定义作者信息:MAINTAINER juno xxxx@xxxx.com
- 执行安装 vim 命令: RUN yum install -y vim
- 定义默认的工作目录:WORKDIR /usr
- 定义容器启动执行的命令:CMD /bin/bash
-
通过 dockerfile 构建镜像:
docker bulid –f dockerfile文件路径 –t 镜像名称:版本
案例二:发布 SpringBoot 项目
实现步骤:
-
文件内容:
- 定义父镜像:FROM java:8
- 定义作者信息:MAINTAINER juno xxxx@xxxx.com
- 将jar包添加到容器:ADD springboot.jar app.jar
- 定义容器启动执行的命令:CMD java–jar app.jar
-
通过 dockerfile 构建镜像:
docker bulid –f dockerfile文件路径 –t 镜像名称:版本