theboyaply

学,就硬学!

  • Home
  • Archives
  • Java
  • Maven
  • Docker
  • Kubernetes
  • JavaScript
  • ES6
  • Vue
  • 踩坑记录
  • noted

  • 搜索
element ui vue wsimport webservice npm mysql redis node nginx nfs ftp es6 开发工具 vscode 前端 javascript springboot 常见问题 tomcat oracle jenkins maven k8s Linux gitlab docker java

Dockerfile

发表于 2020-03-09 | 分类于 Docker | 0 | 阅读次数 461
  • Dockerfile 制作镜像示例
    • 开发一个可执行 jar 包
    • 编写 Dockerfile
    • 制作镜像
  • Dockerfile 介绍
    • 构建镜像上下文
    • FROM 指定基础镜像
    • RUN 执行命令
    • COPY 复制文件
    • ADD 复制文件
    • CMD 容器启动命令
    • ENTRYPOINT 入口点
    • ENV 环境变量
    • ARG 构建参数
    • VOLUME 定义匿名卷
    • EXPOSE 声明端口
    • WORKDIR 工作目录
    • USER 指定后续步骤用户
    • HEALTHCHECK 健康检查
    • ONBUILD 延迟构建

参考:

https://vuepress.mirror.docker-practice.com/image/dockerfile/

https://www.runoob.com/docker/docker-dockerfile.html

Dockerfile 制作镜像示例

我们先制作一个简单的镜像来演示下制作过程。

开发一个可执行 jar 包

我们以 springboot 为例,开发一个端口为 8080 的应用服务,提供一个 /hello?name=tom 接口,并返回 name 的值。

访问 spring.io ,输入相关信息(全部使用默认值即可),添加 web 依赖,然后下载源码并解压。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @GetMapping("/hello")
    public String hello(String name) {
        return "name is: " + name;
    }

}

为了简化开发,这里直接在启动类上添加 hello 接口(请自行测试代码正确性)。将开发好的代码打包为可执行 jar 包,使用相关工具将 jar 包上传到服务器,更名为 demo.jar 。

编写 Dockerfile

在服务器上创建一个文件夹 /dockerDir/ (路径及文件夹名称随意,jar 包也放入这个文件夹下)

在 /dockerDir/ 文件夹中创建一个名为 Dockerfile 的文件,其内容如下:

# 基础镜像(如没有会自动从远程仓库拉取)
FROM openjdk:8

# 将宿主机中的 demo.jar 包添加到容器 app.jar 中
ADD demo.jar app.jar

# 暴漏容器 8080 端口
EXPOSE 8080

# 容器启动时需要运行的命令
ENTRYPOINT exec java -Djava.security.egd=file:/dev/./urandom -jar /app.jar

制作镜像

使用下面的命令制作镜像 :

docker build -t my-server:v1 .
  • docker build:运行Dockerfile 制作镜像的命令
  • -t my-server:v1:镜像的名称及标签,需要注意的是镜像名称不能出现大写
  • 最后的一个 '.':表示 Dockerfile 的上下文路径

使用 docker build 时,默认情况下会寻找当前目录下的 Dockerfile文件,实际上我们可以使用 -f 参数指定文件所在路径以及文件名称,例如:-f /myfolder/dockerfile.txt

docker-build-demo

上面我们已经成功的构建了一个名为 my-server:v1 的镜像了,下面我们试着运行它:

# 将容器的 8080 端口映射到宿主机的 80 端口,容器名称为 demoServer
[root@localhost dockerDir]# docker run -d -p 80:8080 --name demoServer my-server:v1
f54b8c2f08b21b288b493877c8b29504a722afa4184cac54f52df06cff383b53

# 查看容器
[root@localhost dockerDir]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
f54b8c2f08b2        my-server:v1        "/bin/sh -c 'exec ja…"   13 seconds ago      Up 11 seconds       0.0.0.0:80->8080/tcp   demoServer

至此,制作镜像以及运行镜像的流程已经走完了。

Dockerfile 介绍

Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

示例中的 Dockerfile 使用到了 FROM ADD EXPOSE ENTRYPOINT 等指令,实际上 Dcokerfile 还有很多其它指令,但是在介绍这些指令之前,我们先来介绍下 构建镜像上下文。

构建镜像上下文

示例中的命令 docker build -t my-server:v1 . ,其最后一个 . 表示当前目录,也就是 /dockerDir 目录(即上下文路径)。

示例中的 Dockerfile 里存在这么一条指令 ADD demo.jar app.jar ,其中的 demo.jar 就是上下文中的 demo.jar ,也就是 /dockerDir 目录下的 demo.jar。

通过上面的解释我们知道了 . 的作用之后,那么就能理解 docker build -t my-server:v1 . 这条命令是可以替换为 docker build -t my-server:v1 /dockerDir 的。

那么为什么要引入上下文这个概念呢?这里引用 链接 中的描述:

首先我们要理解 docker build 的工作原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。

当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?

这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。

那么为什么会有人误以为 . 是指定 Dockerfile 所在目录呢?这是因为在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。

这只是默认行为,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile。

当然,一般大家习惯性的会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。

FROM 指定基础镜像

FROM 指定了一个基础镜像,我们构建的所有镜像,都需要建立在一个基础镜像之上,因此 FROM 必须是第一条指令。示例中我们的基础镜像就是 openjdk ,在制作的过程中,如果本地没有事先拉取基础镜像,在制作的过程中会自动拉取。

除了 openjdk之外,Docker Hub 上还提供很多现有的镜像,可自行查看。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,比如 swarm、etcd。对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。

RUN 执行命令

RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

  • shell格式:

    RUN <命令>

    <命令> 等同于在终端操作的 shell 命令。

  • exec格式:

    RUN ["可执行文件", "参数1", "参数2"]

    例如:RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline

注意:

Dockerfile 的指令每执行一次都会在 docker 上新建一层。所以过多无意义的层,会造成镜像膨胀过大。

例如:

FROM centos
RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz

# 以上执行会创建 3 层镜像。可简化为以下格式:
FROM centos
RUN yum install wget \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && tar -xvf redis.tar.gz

如上,以 && 符号连接命令,这样执行后,只会创建 1 层镜像。

COPY 复制文件

格式:

  • COPY [--chown=:] <源路径>... <目标路径>
  • COPY [--chown=:] ["<源路径1>",... "<目标路径>"]

和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。

COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如:

COPY package.json /usr/src/app/

<源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则,如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。

此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。

在使用该指令的时候还可以加上 --chown=: 选项来改变文件的所属用户及所属组。

COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/

ADD 复制文件

ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。

比如 ADD 的源文件可以是 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。

另外,如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。

因此我们可以明确区分 ADD 和 COPY 的使用场景,即:仅复制文件使用 COPY,需要解压使用 ADD。

同时 ADD 也支持使用--chown=: 选项来改变文件的所属用户及所属组。

CMD 容器启动命令

CMD 指令的格式和 RUN 相似,也是两种格式:

  • shell 格式:CMD <命令>
  • exec 格式:CMD ["可执行文件", "参数1", "参数2"...]
  • 参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。

在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。

二者运行的时间点不同:

  • RUN 是在 docker build 时执行
  • CMD 在 docker run 时运行

CMD 的作用是为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被docker run 命令行参数中指定要运行的程序所覆盖。

注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。

ENTRYPOINT 入口点

ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。

ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。

但是, 如果运行 docker run 时使用了 --entrypoint 选项,此选项的参数可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。

优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

可以搭配 CMD 命令使用:一般是变参才会使用 CMD,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。

示例:

假设已通过 Dockerfile 构建了 nginx:test 镜像:

FROM nginx

# # 定参
ENTRYPOINT ["nginx", "-c"]

# 变参 
CMD ["/etc/nginx/nginx.conf"]
  1. 不传参运行

    docker run  nginx:test
    

    容器内会默认运行以下命令,启动主进程:

    nginx -c /etc/nginx/nginx.conf
    
  2. 传参运行

    docker run  nginx:test -c /etc/nginx/new.conf
    

    容器内会默认运行以下命令,启动主进程( /etc/nginx/new.conf :假设容器内已有此文件):

    nginx -c /etc/nginx/new.conf
    

ENV 环境变量

格式有两种:

  • ENV <key> <value>
  • ENV <key1>=<value1> <key2>=<value2>...

设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。

例如:

ENV NODE_VERSION 7.2.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
  && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"

下面的例子演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的

ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

ARG 构建参数

格式:ARG <参数名>[=<默认值>]

构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。

Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。

VOLUME 定义匿名卷

格式为:

  • VOLUME ["<路径1>", "<路径2>"...]
  • VOLUME <路径>

因为容器运行时产生的各种数据变化,仅在容器内部,一旦容器丢失数据也会随之丢失。对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。

为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME /data

这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:

docker run -d -v /myData:/data xxx

在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。

EXPOSE 声明端口

格式为 EXPOSE <端口1> [<端口2>...]

仅仅只是声明端口。

作用:

  • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
  • 在运行时使用随机端口映射时,也就是 docker run -p 时,会自动随机映射 EXPOSE的端口。

要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。

WORKDIR 工作目录

格式为 WORKDIR <工作目录路径>。

使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。

简而言之就是,构建镜像时,所有构建步骤都位于 WORKDIR指定的这个目录进行。

USER 指定后续步骤用户

格式:USER <用户名>[:<用户组>]

USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。

当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

HEALTHCHECK 健康检查

格式:

  • HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
  • HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。

HEALTHCHECK 支持下列选项:

  • --interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
  • --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
  • --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。

和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。

在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。

假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写:

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
  CMD curl -fs http://localhost/ || exit 1

ONBUILD 延迟构建

格式:ONBUILD <其它指令>。

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。

--end--

# docker
Docker镜像
使用certbot-auto生成HTTPS证书
  • 文章目录
  • 站点概览
theboyaply

theboyaply

好记性不如烂笔头

184 日志
13 分类
27 标签
Github E-mail
Creative Commons
0%
© 2019 — 2023 theboyaply
由 Halo 强力驱动
|
主题 - NexT.Gemini
湘ICP备19009291号

湘公网安备 43312402001034号