Dockerfile专题 | 如何构造自己的docker镜像

2024-08-30 15:59:22 浏览数 (2)

前言

说起docker,大家都知道是容器。我们从仓库中docker pull拉取镜像(image)到本地,然后docker run指定镜像来启动一个容器(container)。那么,这个容器就开始守护内部的程序。

但是很多时候,我们需要在docker中部署自己的程序,或者因为pull的镜像太大,我们需要基于一个小的镜像来重构,这就需要我们要自己构建docker,而docker的构建步骤是基于Dockerfile来实现的。

Dockerfile

在学习Dockerfile之前,首先弄明白镜像。容器就类似于“虚拟机”,而虚拟机就需要操作系统,这个操作系统就是镜像(image)。

image

通过docker images就可以查看宿主机上已存在的所有镜像。

在上面的镜像中,使用比较多的就是centos,但是centos里面已经预装了很多软件,所以大小为231MB。像alpine、busybox的镜像就比较小,很多命令都需要自己安装,但是实际中也不会过多地去容器中执行很多的命令。

指令

首先文件名必须是Dockerfile,第一行必须使用FROM指令来引入镜像,最后一行是通过CMD或者ENTRYPOINT指令来指定容器中要运行的命令。

构建镜像是由一个基础镜像开始,通过经过各种指令来构造成成我们自己的镜像

  1. FROM:第一行必须是 FORM centos,表示从centos镜像构建容器
  2. ENV key value:设置环境变量
  3. RUN command:在之前的镜像基础上执行指令,并提交为新的镜像
  4. EXPOSE 80:开放80端口,run时需要用 -p来指定映射端口,不指定默认分配
  5. VOLUME /data:声明容器中/data为匿名卷。-v可以将这个匿名卷绑定到宿主机,不指定会自动绑定到/var/lib/docker/volumes
  6. ADD src dest:文件必须和Dockerfile同一目录,除了复制本地文件到容器中外,还有解压缩文件的功能
  7. COPY src dest:文件必须和Dockerfile同一目录,将src目录/文件复制到容器的dest目录
  8. CMD:容器启动时执行的命令
  9. ENTRYPOINT:和CMD一样,指定容器启动执行的命令

上面就是在Dockerfile中常用的命令。看到上面可能会有疑问,CMD 和 ENTRYPOINT不是一样的命令吗?

CMD和ENTRYPOINT

CMD 指令用于为镜像指定一个默认的命令和参数,但该命令可以在运行容器时被指定的命令覆盖。我们还记得最初学习docker时,运行容器时使用docker run,在最后面会加一个/bin/bash,这个/bin/bash就是一个指令,它会覆盖CMD。

代码语言:bash复制
docker run my-image echo "Hello, World!"

同样,这里,echo "Hello, World!" 将覆盖 Dockerfile 中的 CMD 指令。

而ENTRYPOINT指令用于配置一个容器启动时将始终执行的主命令,就不会有这种被覆盖的问题,它会默认把docker run指定的指令当做自己的参数,同样

代码语言:bash复制
docker run my-image "Hello, World!"

这里 "Hello, World!" 将作为参数传递给 ENTRYPOINT 中的命令,而不会替换掉它。有时CMD和ENTRYPOINT也会搭配使用,ENTRYPOINT 用于指定固定的命令,而 CMD 用于提供默认参数。这样,可以在运行容器时添加或替换 CMD 的默认参数。

例如我使用下面的Dockerfile构造镜像之后:

代码语言:bash复制
FROM centos
ENTRYPOINT ["echo"]
CMD ["Hello, World!"]

运行容器时不指定命令,就会输出"Hello, World!",如果我指定命令:

代码语言:bash复制
docker run my-image "Hello, Docker!"

那么, "Hello, Docker!"就会覆盖CMD指定的 "Hello, World!" ,最后输出 "Hello, Docker!"

Tomcat的Dockerfile

这里我就基于centos镜像,编写Dockerfile,来构造一个Tomcat的镜像。在构造一个镜像之前,我们还需要考虑这个镜像依赖的环境变量、下载的软件包以及开放的命令等等。

如果我们在服务器上搭建一个Tomcat,我们需要下载tomcat,然后配置jdk、开放8080端口,然后在tomcat的bin目录下,执行startup.sh,这样一个tomcat服务就启动成功了。我们要做的就是,把这些操作转换到Dockerfile中。

Dockerfile

首先我下载了tomcat的安装包,在宿主机的/data目录下,同时宿主机的jdk放在了/usr/local下。如何把tomcat和jdk放到镜像中,这时候有的同学就要抢答了:“COPY!”

其实比较好的方案是使用VOLUMN指令,将宿主机的目录挂载到容器中,这样每个容器使用的就是宿主机上的文件,而减少了对磁盘存储的占用。

代码语言:bash复制
FROM centos
VOLUME /usr/local/jdk
VOLUME /usr/local/tomcat
ENV JAVA_HOME /usr/local/jdk
ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin
EXPOSE 8080
CMD catalina.sh run

在Dockerfile中,我在/usr/local下一共声明了两个卷:jdk和tomcat,然后在后面的ENV指令中,将这两个卷作为jdk和tomcat的环境变量目录,配置在了PATH中。

然后在CMD中执行catalina.sh run的指令,这里不能使用startup.sh,因为docker容器是为了守护一个进程/命令而存在的,所以必须必须使用前台启动方式

构造镜像

然后构造开始构造镜像:

代码语言:bash复制
docker build -t="tomcat-aqi" .

构造成功之后,通过docker images可以查看到我们的镜像。

运行容器

根据dockerfile中配置,在docker run命令中,我们要将宿主机中的jdk和tomcat目录,挂载到容器的卷中,然后容器8080端口映射为宿主机的9092。

代码语言:bash复制
docker run -d --name tomcat-qi -p 9092:8080 
-v /data/apache-tomcat-8.0.11:/usr/local/tomcat 
-v /usr/local/jdk1.8.0_201:/usr/local/jdk  
-it tomcat-aqi

在容器运行成功之后,通过docker ps可以查看到进程。

在浏览器中通过服务器的9092的端口,就访问到了容器中的tomcat。

在容器信息中,我们可以看到挂载信息,宿主机上的JDK和tomcat目录已经被挂载到了容器卷中。

也可以看到容器的ENV环境变量和CMD信息。

镜像层

镜像层(Image Layers)是 Docker 镜像的一个重要概念。每个 Docker 镜像实际上是由多层文件系统(Union File System)组成的,每一层都是只读的,称为镜像层。这些层次是镜像的基础,每个层次代表镜像构建过程中的一个步骤或一个命令。

每一层都是一个增量修改,相当于给上一层加上了新的变化。这样,Docker 镜像的每一层都是不可变的。

Docker 通过层来实现缓存机制。例如,如果你修改了 Dockerfile 中的某一行,Docker 只会从该行开始重新构建层,而之前的层都被缓存并且重用。这大大提高了构建速度

减少镜像层数和每层的大小可以优化镜像的体积和传输速度。过多的层数会导致冗余数据的增多和构建时间的增加。我们可以通过将多个 RUN 指令合并为一个,来减少镜像层。

我们在上面的Dockerfile中加入三行RUN指令:

代码语言:bash复制
RUN ps
RUN echo 1
RUN date

再将三条RUN指令合并:

代码语言:bash复制
RUN ps && echo 1 && date

分别使用两个Dockerfile构造镜像,从构造过程日志可以看出,指令合并前镜像层为8,合并后为6。

结语

最后讲讲为什么使用centos,而不是alpine镜像。原因就是alpine需要下载相应版本的OpenJDK,不支持我这个oracle的JDK,所以我还是走了捷径,直接使用了centos。

0 人点赞