Docker-Dockerfile

简介
学习完 Docker 后你一定有这样的以为,那这些镜像从何而来?比如说以前的 redis 它是怎么变成一个镜像供大家使用的? 我们能不能把自己的 SpringBoot 项目也变成一个镜像?如何制作自定义镜像就是本文要探讨的了。
Dockerfile 是一个文本文件,里面包含了一条条的指令(instruction),这些指令告诉 Docker 如何一步一步地构建一个镜像。你可以把它想象成一个制作菜谱,Dockerfile 就是这个菜谱,里面详细说明了每一步的操作,最终输出的就是一道美味的菜肴——一个定制化的 Docker 镜像。
Dockerfile 的作用:
- 自动化构建镜像: 通过 Dockerfile,我们可以将镜像构建过程自动化,避免手动执行繁琐的命令。
- 复用性: 一个 Dockerfile 可以用来构建多个相同的镜像,提高了效率。
- 可读性: Dockerfile 使用简单的指令,易于阅读和理解。
- 可维护性: 可以方便地修改 Dockerfile 来更新镜像。
Dockerfile 仅仅是一个文件,依据文件的配置来制作镜像,它是 Docker 引擎自带的功能,所以不需要另外安装任何东西。
官方文档:https://docs.docker.com/reference/dockerfile/
原理
Dockerfile 的基本指令并不多,不用一次记住,用到的时候,慢慢理解就行。
- FROM: 指定基础镜像。
- MAINTAINER: 指定镜像维护者。
- RUN: 在镜像中执行命令。
- COPY: 将本地文件复制到镜像中。
- ADD: 和 COPY 类似,但可以从 URL 下载文件。
- EXPOSE: 声明容器暴露的端口。
- ENV: 设置环境变量。
- WORKDIR: 设置工作目录。
- VOLUME: 创建挂载点。
- CMD: 指定容器启动时默认执行的命令。
- ENTRYPOINT: 指定容器启动时执行的命令,与 CMD 类似,但优先级更高。
Docker 镜像并不是一个简单的一体化文件,而是由多层叠加而成的。每一层就代表着 Dockerfile 中的一条指令,这些层按照顺序叠加起来,最终形成一个完整的镜像。所以过多无意义的层,会造成镜像膨胀过大,构建的速度就慢。
形象地说,Docker 镜像就像是一座大楼,每一层代表着大楼中的一层楼。每一层楼都有自己的功能和作用,这些楼层一层一层叠加起来,最终形成了整座大楼。
镜像层的特点是
- 只读: 每一层都是只读的,也就是说,一旦创建了一个层,就不能对其进行修改。
- 复用: 如果后续的镜像构建过程中,某一步操作与之前已经创建的层完全一致,那么 Docker 会直接复用该层,而不需要重新构建。
- 缓存: Docker 会缓存每一层,这样在后续的构建过程中,如果底层的镜像没有发生变化,就可以直接使用缓存中的层,从而加快构建速度。
镜像的工作原理
- 基础层: 每一层都基于前一层构建,最底层通常是一个基础镜像,比如 Ubuntu、Alpine 等。
- 指令层: Dockerfile 中的每一条指令都会创建一个新的层。例如,
RUN apt-get update && apt-get install -y python3
这条指令会创建一个新的层,在这个层中安装了 Python3。 - 层叠加: 这些层按照顺序叠加起来,形成一个完整的镜像。当运行容器时,Docker 会将这些层联合起来,形成一个可读写的容器层。
我们从最基础的镜像来说起吧。创建一个 Dockerfile 文件,这个文件是没有后缀名的
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
最后在 Dockerfile 所在目录执行docker build -t my_python:v3 .
命令构建镜像。
这个 Dockerfile 会创建一个包含以下层的镜像:
- 基础层: Python 3.9-slim-buster 镜像。
- 工作目录层: 设置工作目录为
/app
。 - 复制 requirements.txt 层: 将
requirements.txt
文件复制到/app
目录。 - 安装依赖层: 安装
requirements.txt
中列出的依赖。 - 复制代码层: 将项目代码复制到
/app
目录。 - 运行容器后执行 app.py 脚本。
docker build -t my_python:v3 .
命令的解释:
- docker build: 这是 Docker 的构建命令,用于根据 Dockerfile 创建一个新的镜像。
- -t my_python:v3: 这个选项用来给新构建的镜像打上标签。
my_python
是镜像的名称,v3
是镜像的版本号。 - . (点号): 这个点表示构建上下文,也就是 Dockerfile 所在的目录。Docker 会从这个目录中读取 Dockerfile 并开始构建。
重点
一个基础的 Dockerfile 学会后,我们再回头去理解下镜像层和缓存,这非常非常重要,一定要理解清楚。
-
既然每个指令都是一层,那么为了避免层数膨胀,我们应该尽量减少层数,以加快镜像制作。
RUN apt-get update RUN apt-get install -y package1
将上述两层合并成一层
RUN apt-get update && apt-get install -y package1
-
Docker 会将 Dockerfile 所在目录下的所有文件都包含在镜像中,一些不必要的文件我们可以通过
.dockerignore
文件来排除,以减轻镜像的体积。 -
每个指令的变更,位置移动,都会改变层,使其后的层缓存失效。
-
将经常更改的层放到后面,充分利用镜像层的缓存,加快构建。
-
缓存有时候也不是个好东西, 比如某个镜像依赖一个包,这个包更新了,下次构建镜像的时候用了缓存层,导致构建出的镜像还用的以前的包。这个坑可是我踩过的!但是想半天没想通为什么新包没其效果。我提供三种方式来强制不适用缓存构建镜像。
- 构建镜像时通过参数
--no-cache
禁用缓存,比如:docker build --no-cache -t my_python:v3
- 在构建前通过
docker system prune -a --force
命令删除全部缓存。 - 在 Dockerfile 中添加一个自定义参数,每次构建的时候,传递一个新值改变这个自定义参数,使得其后的层都不使用缓存。
比如设置一个自定义参数 CACHEBUST# ... 可以使用缓存的其他指令 ... ARG CACHEBUST=1 # ... 不希望它使用缓存的其他指令 ...
在构建时传递不同的值,强制其后的层不使用缓存。
docker build --build-arg CACHEBUST=2 -t my_image .
- 构建镜像时通过参数
Dockerfile 构建 java 项目镜像
目标是将传统的 Springboot 项目制作成镜像,并根据这个镜像启动项目容器。
首先是要在项目中写一个Dockerfile 文件,然后根据该文件制作镜像,最后启动容器。java 项目就通过容器跑起来了。
-
在 Springboot 项目根目录下创建 Dockerfile 文件,文件内容如下:
FROM openjdk:11.0.2-jdk LABEL maintainer=systemcaller COPY target/*.jar /app.jar ENTRYPOINT ["java","-jar", "/app.jar"]
配置文件解析:
FROM openjdk:11.0.2-jdk
是以jdk为基础运行环境。LABEL maintainer=systemcaller
是标镜像制作者是systemcaller 。COPY target/*.jar /app.jar
表示从target目录下*.jar
文件复制到容器内的/app.jap
文件,注意 target 和 Dockerfile 都在项目根目录下。ENTRYPOINT ["java","-jar", "/app.jar"]
表示运行容器的命令是java -jar /app.jar
。
-
将 target 目录和 Dockerfile 文件上传到服务器上,构建镜像并启动容器。
[root@VM-4-14-centos java]# pwd /root/java [root@VM-4-14-centos java]# ls Dockerfile target [root@VM-4-14-centos java]# docker build -t javademo:v1.0 . [root@VM-4-14-centos java]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE javademo v1.0 f4c1b5ccbde5 38 seconds ago 849MB [root@VM-4-14-centos java]# docker run -d --name myjava -p 8080:8080 javademo:v1.0