Dockerfile

发布于 1970年 01月 01日 08:00

Docker File

Docker 镜像原理

思考

  • Docker 镜像本质是什么?
  • Docker 中一个 centos 镜像为什么只有 200MB,而一个 centos 操作系统的 iso 文件要几个 G ?
  • Docker 中一个 tomcat 镜像为什么有 500MB,而一个 tomcat 安装包只有 70 多 MB ?

image

操作系统组成

  • 进程调度子系统
  • 进程通信子系统
  • 内存管理子系统
  • 设备管理子系统
  • 文件管理子系统
  • 网络通信子系统
  • 作业控制子系统

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 会在最顶层加载一个读写文件系统作为容器。

image

总结

  • 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 镜像如何制作?

image

  • 方法一:容器转为镜像
docker commit 容器id 镜像名称:版本号
docker save -o 压缩文件名称 镜像名称:版本号
docker load –i 压缩文件名称
  • 方法二:使用 Dockerfile

Dockerfile 概念

什么是 Dockerfile ?

  • Dockerfile 是一个文本文件,包含了一条条的指令。
  • 每一条指令构建一层,基于基础镜像,最终构建出一个新的镜像。
  • 对于开发人员:可以为开发团队提供一个完全一致的开发环境。
  • 对于测试人员:可以直接拿开发时所构建的镜像或者通过 Dockerfile 文件构建一个新的镜像开始工作。
  • 对于运维人员:在部署时,可以实现应用的无缝移植。
  • 官方 Dockerfile 文件参考:https://github.com/CentOS/CentOS-Dockerfiles

image

Dockerfile 主要组成部分

  1. 基础镜像信息:如 FROM centos:6.8
  2. 制作镜像时的操作命令:如 RUN yum insatll openssh-server -y
  3. 容器启动时的执行命令:如 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 命名。

image

这个 Dockerfile 只有一行 from 命令,即引用 nginx 这个基础镜像,剩余的动作完全没有。这并没有实际意义,除了演示不会有人这么做 Dockerfile 的。

# 构建镜像
docker build -t nginx:v1from .

image

注意

  • 最后的“.”,代表从本目录的 Dockerfile 执行,也可以使用绝对路径。
  • 打镜像的时候最后镜像名称和t ag 都由自己规划,即“:”前后都是自定义。
  • 如果名称和 tag 和之前版本重复,相当于提交新版本并覆盖原有镜像。

使用新做的 nginx:v1from 镜像启动容器:

image


RUN 命令

用于执行后面跟着的命令行命令。有两种格式:

RUN <命令行命令>
RUN ["可执行文件", "参数1", "参数2", ...]

仍然以 nginx 为基础镜像,这次增加一个 RUN 命令:

image

说明:可以到 docker.com 上查看 nginx 镜像的说明文档,找到这个镜像中 nginx 默认 web 目录的位置为:/usr/share/nginx/html/,并使用 echo 命令打印一句话并重定向到该目录下的 index.html 文件中。

容器启动后,再通过 nginx 看到这个静态页面查看命令执行结果。

image

image

image

可见默认文件 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

image

结论

  • RUN 下载的文件,在同一个 RUN 中进行了删除,才能减小镜像大小(写时复制和联合挂载的特性)。
  • RUN 的个数并不是越少越好,细分后的 RUN 更容易在构建时候被当做 cache 利用到,一个 RUN 里过多的 && 连接命令则很难被复用。

COPY 命令

COPY 命令用于将本地文件或者目录拷贝进容器镜像。

如下图,构建 dockerfile,目录 web,web 下有一个 a.html 文件,文件中一句话。

image

image

启动容器并从浏览器中查看目录下的文件:

image

注意事项

  • 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 脚本。

image

Dockerfile 说明

  1. yum 安装的 nginx,默认页面存放路径依然是 /usr/share/nginx/html;
  2. 该例子首先拷贝一个静态页面 default.html 到默认路径中;
  3. 然后新建一个静态页面路径,将 modify.html 文件拷贝到新建的路径中;
  4. COPY 一份修改后的配置文件到 /nginx 下;
  5. 最后用 CMD 启动 nginx,注意 -g daemon off; 是容器启动必须加的参数(别漏写“;”),以避免 nginx 后台启动,否则 nginx 后台启动将导致容器直接退出。

1)docker run 时不加启动参数

注意,此处 Dockerfile 中未加入 EXPOSE,需要指定后端端口,用小写 -p 启动:

docker run -d -p 80:80 nginx:v4cmd

image

2)docker run 时加启动参数

docker run -d -p 81:80 nginx:v4cmd nginx -g "daemon off;" -c /nginx/nginx.conf

image

可以看到,若启动时带了运行参数,则会覆盖掉 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 时不加启动参数

image

image

2)docker run 时加启动参数

docker run -d -p 81:80 nginx:v5entrypoint -g "daemon off;" -c /nginx/nginx.conf

image

可以看到,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 时不带参数

image

image

2)docker run 时带参数

image

image

实际使用中,ENTRYPOINT 和 CMD 组合的方式会更常见一些


VOLUME 命令

实现效果和 docker run -v 挂载主机目录一样。

写在 Dockerfile 中的好处是,万一在 docker run 时候忘记了 -v 参数,数据也不会丢失。

另外 docker run -v 指令会覆盖 Dockerfile 的 VOLUME 指令。


案例一:自定义 centos7 镜像

需求:自定义 centos7 镜像

  1. 默认登录路径为 /usr
  2. 可以使用 vim

实现步骤

  • 文件内容:

    1. 定义父镜像:FROM centos:7
    2. 定义作者信息:MAINTAINER juno xxxx@xxxx.com
    3. 执行安装 vim 命令: RUN yum install -y vim
    4. 定义默认的工作目录:WORKDIR /usr
    5. 定义容器启动执行的命令: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 镜像名称:版本