Skip to content

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 项目的镜像结构如图所示:

    dockerfile

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 如何转化为容器映像中的层堆栈。

From Dockerfile to layers

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 内容如下:

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 包就行。

dockerfile
# 基础镜像
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
  • 执行命令后,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所在目录