09 Docker 基础 - Dockerfile 语法
镜像结构
Dockerfile
Dockerfile 是一个文本文件,其中包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令都会对镜像进行修改。Dockerfile 包含了构建镜像所需的所有指令,可以通过 Dockerfile 构建出一个完整的镜像。
要想自己构建镜像,必须先了解镜像的结构。
之前我们说过,镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。
因此,自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
举个例子,我们要从 0 部署一个 Java 应用,大概流程是这样:
- 准备一个 linux 服务(CentOS 或者 Ubuntu 均可)
- 安装并配置 JDK
- 上传 Jar 包
- 运行 jar 包
那因此,我们打包镜像也是分成这么几步:
- 准备 Linux 运行环境(java 项目并不需要完整的操作系统,仅仅是基础运行环境即可)
- 安装并配置 JDK
- 拷贝 jar 包
- 配置启动脚本
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一 id,称为Layer(层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
例如,第一步中需要的 Linux 运行环境,通用性就很强,所以 Docker 官方就制作了这样的只包含 Linux 运行环境的镜像。我们在制作 java 镜像时,就无需重复制作,直接使用 Docker 官方提供的 CentOS 或 Ubuntu 镜像作为基础镜像。然后再搭建其它层即可,这样逐层搭建,最终整个 Java 项目的镜像结构如图所示:

Layers
The order of Dockerfile instructions matters. A Docker build consists of a series of ordered build instructions. Each instruction in a Dockerfile roughly translates to an image layer. The following diagram illustrates how a Dockerfile translates into a stack of layers in a container image. Dockerfile 指令的顺序很重要。Docker 构建由一系列有序的构建指令组成。Dockerfile 中的每条指令大致相当于一个映像层。下图说明了 Dockerfile 如何转化为容器映像中的层堆栈。

Dockerfile
- 由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以 Docker 就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给 Docker 去执行即可。
- 而这种记录镜像结构的文件就称为Dockerfile,其对应的语法可以参考官方文档:Dockerfile reference | Docker Docs
Dockerfile 指令
| 指令 | 说明 | 示例 |
|---|---|---|
| FROM | 指定基础镜像 | FROM ubuntu:18.04 |
| MAINTAINER | 指定镜像维护者信息 | MAINTAINER Ryanjie |
| RUN | 在镜像中执行 shell 命令,每执行一次就会在镜像上新建一层 | RUN apt-get update |
| CMD | 指定容器启动时要运行的命令 | CMD ["nginx", "-g"] |
| EXPOSE | 指定容器对外暴露的端口,是给镜像使用者看的 | EXPOSE 80 |
| ENV | 设置环境变量 | ENV JAVA_HOME /usr |
| ADD | 将文件或目录复制到镜像中 | ADD ./test.txt / |
| COPY | 将文件或目录复制到镜像中 | COPY ./test.txt / |
| ENTRYPOINT | 指定容器启动时要运行的命令 | ENTRYPOINT ["nginx"] |
| VOLUME | 指定容器中的数据卷 | VOLUME /data |
Dockerfile 示例
基于 Ubuntu 镜像来构建一个 Java 应用,其 Dockerfile 内容如下:
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK 的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
ENV TZ=Asia/Shanghai
# 拷贝 jdk 和 java 项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 设定时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安装 JDK
RUN cd $JAVA_DIR \
&& tar -xf ./jdk8.tar.gz \
&& mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 指定项目监听的端口
EXPOSE 8080
# 入口,java 项目的启动命令
ENTRYPOINT ["java", "-jar", "/app.jar"]将 Linux 系统环境、JDK 环境这两层合并成一个基础镜像 openjdk:11.0-jre-buster,以后制作 java 镜像只需要配置 jar 包就行。
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝 jar 包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]构建镜像
实验资料
demo 项目 (demo.jar) 及对应的 Dockerfile 文件在课程资料中。目录:资料 → demo。
当 Dockerfile 文件写好以后,就可以利用命令来构建镜像了。
将课前资料提供的
docker-demo.jar包以及Dockerfile拷贝到虚拟机的/root/demo目录。bash❯ tree . ├── docker-demo.jar └── Dockerfile 0 directories, 2 files然后执行
docker build -t demo:1.0 .命令构建镜像。docker build:构建镜像的命令-t demo:1.0:指定镜像的名称(repository)和版本(tag),名称为demo,版本号为1.0,不指定 tag 时,默认为latest.:指定 Dockerfile 文件所在的目录,这里是当前目录。- 如果 Dockerfile 文件名不是
Dockerfile,则需要使用-f参数指定文件名。docker build -t demo:1.0 -f Dockerfile.dev . - 如果 Dockerfile 文件不在当前目录,可以使用绝对路径或者相对路径指定文件所在目录。
docker build -t demo:1.0 /root/demo
- 如果 Dockerfile 文件名不是
执行命令后,Docker 会按照 Dockerfile 文件中的指令逐层构建镜像,最终生成一个镜像文件。
bash❯ docker build -t demo:1.0 . [+] Building 7.6s (8/8) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 299B 0.0s => [internal] load .dockerignore 0.1s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/openjdk:11.0-jre-buster 1.2s => [1/3] FROM docker.io/library/openjdk:11.0-jre-buster@sha256:569ba9252ddd693a29d39e81b3123481f308eb6d 5.4s => => resolve docker.io/library/openjdk:11.0-jre-buster@sha256:569ba9252ddd693a29d39e81b3123481f308eb6d 0.0s => => sha256:fa46ae940938ca17b5634fac0d58da875f0d7c53688cb7a8a4d6ac47f658d4d3 1.58kB / 1.58kB 0.0s => => sha256:0b489110c503fc781fea676ea3550679969b5de8bd237c21eb5dca7077ef2869 7.52kB / 7.52kB 0.0s => => sha256:cc915d298757b72963f0d061cc16ca4925e9f4481446b87a5297b4043ffc8033 10.00MB / 10.00MB 0.6s => => sha256:569ba9252ddd693a29d39e81b3123481f308eb6d529827a40c93710444e421b0 549B / 549B 0.0s => => sha256:7e6a53d1988fa8e19db6bcfc96ee6783afb079c38dbe047528e691815d19a9fa 50.44MB / 50.44MB 1.0s => => sha256:4fe4e1c58b4af82939a918665dd1e7b5b636dd73c710b4bccb530edbb15470d2 7.86MB / 7.86MB 0.6s => => sha256:6cd61a4b7a06678967e883d8b11485979d28989d5306ba06bf2c6b483c05b516 211B / 211B 0.9s => => sha256:0f795594794cd5bee4c556ac9e51dde9dface10e4512f611fd067ad2a357d0bd 5.53MB / 5.53MB 0.9s => => sha256:62acc5f6f7aae46e03d13e9f65af350b1bca82d942b72a1c7ff81c012bd384ed 45.77MB / 45.77MB 1.6s => => extracting sha256:7e6a53d1988fa8e19db6bcfc96ee6783afb079c38dbe047528e691815d19a9fa 2.1s => => extracting sha256:4fe4e1c58b4af82939a918665dd1e7b5b636dd73c710b4bccb530edbb15470d2 0.2s => => extracting sha256:cc915d298757b72963f0d061cc16ca4925e9f4481446b87a5297b4043ffc8033 0.2s => => extracting sha256:0f795594794cd5bee4c556ac9e51dde9dface10e4512f611fd067ad2a357d0bd 0.2s => => extracting sha256:6cd61a4b7a06678967e883d8b11485979d28989d5306ba06bf2c6b483c05b516 0.0s => => extracting sha256:62acc5f6f7aae46e03d13e9f65af350b1bca82d942b72a1c7ff81c012bd384ed 0.9s => [internal] load build context 0.2s => => transferring context: 17.70MB 0.1s => [2/3] RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/time 0.6s => [3/3] COPY docker-demo.jar /app.jar 0.2s => exporting to image 0.1s => => exporting layers 0.1s => => writing image sha256:bcdc2a0828279af824cfe27308b3dfa596d26580bbfb5b30741f7832f71c84e4 0.0s => => naming to docker.io/library/demo:1.0 0.0s查看镜像列表。从镜像列表中可以看到,刚刚构建的镜像已经存在了。
bash❯ dkIls # docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE demo 1.0 bcdc2a082827 About a minute ago 315MB nginx latest c20060033e06 8 days ago 187MB mysql latest a3b6608898d6 2 weeks ago 596MB hello-world latest 9c7a54a9a43c 6 months ago 13.3kB运行镜像。运行镜像的命令是
docker run,-d参数表示以后台方式运行,-p参数表示将容器内的端口映射到宿主机的端口上,demo:1.0是镜像的名称和版本号。bash❯ docker run -d --name dockerfile-demo -p 80:8080 demo:1.0 dd02e6e65d395a710674c479b900b7b516492c64497e11c73cabf0074f2bfa85查看容器列表。从容器列表中可以看到,刚刚运行的容器已经存在了。
bash❯ dkps # docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES dd02e6e65d39 demo:1.0 "java -jar /app.jar" 46 seconds ago Up 45 seconds 0.0.0.0:80->8080/tcp, :::80->8080/tcp dockerfile-demo访问应用。
bash❯ curl localhost:80/hello/count <h5>欢迎访问黑马商城,这是您第 1 次访问<h5>% ❯ curl localhost:80/hello/count <h5>欢迎访问黑马商城,这是您第 2 次访问<h5>% ❯ curl localhost:80/hello/count <h5>欢迎访问黑马商城,这是您第 3 次访问<h5>% ❯ ~/demo ❯
总结
- 镜像的结构是怎样的?
- 镜像中包含了应用程序所需要的运行环境、函数库、配置、以及应用本身等各种文件,这些文件分层打包而成。
- Dockerfile 是做什么的?
- Dockerfile 就是利用固定的指令来描述镜像的结构和构建过程,这样 Docker 才可以依次来构建镜像
- 构建镜像的命令是什么?
docker build -t 镜像名:标签名 Dockerfile所在目录