Dockerfile 实践及梳理

  • A+
所属分类:linux技术
摘要

Dockerfile 是一个文本文件,我们可以通过组合一条条的指令 (Instruction),来构建满足我们需求的 Docker 镜像

Dockerfile 是一个文本文件,我们可以通过组合一条条的指令 (Instruction),来构建满足我们需求的 Docker 镜像

文档

Best practices for writing Dockerfiles

Reference

Dockerfile 指令详解

简单上手

使用 Dockerfile 构建SpringBoot 工程的镜像

  1. 新建 SpringBoot 项目,默认的端口是 8080 ,新建 Controller 和 Mapping
@RestController public class HelloController {     @GetMapping("hello")     public String hello() {         return "hello world!";     } } 

启动项目,访问 http://localhost:8080/hello 测试

  1. 打 jar 包
    注意,需要在 pom 中添加 spring-boot-maven-plugin 插件,否则运行 jar 包时会提示:没有主清单属性
    <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build> 
#打包 mvn package 

target 目录下就可以找到 .jar 文件,我这里的文件名为:demo-0.0.1-SNAPSHOT.jar
在 Linux 新建 ~/springboot 文件夹,并将 jar 包上传到这个文件夹下

  1. 新建 Dockerfile
    在这个文件下新建 Dockerfile 文件
# 基于 openjdk:8-jre 这个基础镜像进行构建 FROM openjdk:8-jre  # 这里的 demo-0.0.1- SNAPSHOT.jar 要对应上传的 jar 包名称 # 将 本地 jar包 复制到容器内 COPY demo-0.0.1-SNAPSHOT.jar  app.jar  # 开放 8080 端口 EXPOSE 8080  # 运行命令、参数 ENTRYPOINT ["java","-jar"] CMD ["app.jar"] 

保存文件,退出编辑器

  1. 编译 Docker 镜像
# build 是构建 Docker 镜像的命令 # -t 指定镜像的 tag # 名称:demo 版本:v1.0 # 最后的 . 表示 build context 目录为当前目录,目的是为了找到 所需的 jar 包 docker build -t demo:v1.0 . 
  1. 启动容器
# 前台启动刚构建的 SpringBoot 容器 # -p 映射容器8080端口 到宿主机的 8080 上 docker run -p 8080:8080 demo:v1.0 
  1. 测试
    访问 Linux 的8080 端口,注意替换为自己的 Linux 的地址,并开放 8080 端口

http://192.168.43.161:8080/hello

build context

Dockerfile 默认会使用它自己所在的目录作为 context,通过 docker 执行构建命令后,Docker daemon 会拷贝 context 目录下的所有文件,所以 context 目录不要放置项目无关的文件,或者可以使用 .dockerignore 定义忽略文件,也可以指定 context 路径

# build 命令通过 Dockerfile 构建镜像 # 指定 ~/dockerfile 为 build context docker build ~/dockerfile # 不需要添加文件到 context 可以使用 - docker build - 

可以通过 stdin 的方式,避免生产 Dockerfile 文件,直接 build 镜像

docker build -t myimage:latest -<<EOF FROM busybox RUN echo "hello world" EOF 

除了可以指定 context外,还可以通过-f 指定 Dockerfile 所在的路径

docker build  -f dockerfiles/Dockerfile . 

最佳实践

非常推荐官方的 Dockerfile最佳实践:Best practices for writing Dockerfiles

  1. 每个容器单一职责,有利于横向拓展和复用
  2. 旧版强调减少层数以提高性能,现在只有 RUN, COPY, ADD 这几个命令会创建层,其他命令只会创建中间层。并且只有使用到资源最终会被拷贝到最终镜像
  3. 多个参数按字母顺序排列,并使用空格和 进行分割,提高可读性
  4. --no-cache 不使用缓存,默认 build 过程中如果检查到有可重用的镜像层则使用。从基础镜像开始,每一条命令逐一检查,如果命令不一样则缓存失效。使用 ADDCOPY 则会校验使用到的文件校验和是否相同,除了这两个命令,其他则不会通过文件变化来决定是否匹配缓存,而是仅通过命令本身是否一致来判断是否匹配缓存,比如:RUN apt-get -y update会改变容器内的文件,但是也只使用这个命令匹配缓存,而不会通过文件的变动。一旦缓存失效,后续都会产生新的镜像层

Dockerfile 指令 (instructions)

FROM

Dockerfile 的第一个命令一般都是 FROM,通过这个指定该镜像的 Base Image,推荐基础镜像:alpine,因为它完整且轻量,如果不需要 Base Image 可以用 FROM scratch,代表该镜像基于一个空镜像进行构建

RUN

由于上面提到的缓存匹配原则,RUN apt-get update 命令可能会导致直接使用了原来缓存的镜像层,而没有执行该命令获取最新的软件列表,可以使用 RUN apt-get update && apt-get install -y 来使缓存失效
可以使用 分割,提高可读性:

RUN apt-get update && apt-get install -y      curl 

CMD

指定容器启动时运行的命令,通常默认采用的格式:CMD ["executable", "param1", "param2"…],如:

CMD ["perl", "-de0"] 

这样使用 docker run -it 命令进入容器时,就会默认进入 shell 界面

EXPOSE

指定容器需要监听的端口

ENV

可以使用 ENV 更新 PATH 环境变量,例如

ENV PATH=/usr/local/nginx/bin:$PATH 

注意!每一个 ENV 指令都会创建一个新的中间层 (intermediate layer),如果使用 ENV 设置了变量,在未来的层 unset 了变量,那么它在 unset 之前依然是可用的。为了防止这种情况,我们应该用 RUN 进行环境变量的 设置和取消

ENV ADMIN_USER="mark" RUN echo $ADMIN_USER > ./mark RUN unset ADMIN_USER 

ADD or COPY

两个命令功能相似,优先使用COPY,它的作用只是将本地文件拷贝到容器内,而 ADD 则有其他特性,比如:自动将本地 tar 文件提取到镜像中、远程URL
如果多个步骤需要使用不同的文件,应该单独 COPY,而不是一次性 COPY,这样部分文件变化不会导致所有的缓存都失效
避免使用 ADD 通过 URL 获取包,可以使用 curl 或者 wget,这样可以在提取后删除文件,避免镜像多一层,还可以通过管道,就不需要再手动删除中间文件

RUN mkdir -p /usr/src/things      && curl -SL https://example.com/big.tar.xz      | tar -xJC /usr/src/things      && make -C /usr/src/things all 

ENTRYPOINT

使用 ENTRYPOINT 设置主命令,还可以用 CMD 设置默认的可选参数

ENTRYPOINT ["s3cmd"] CMD ["--help"] 

运行编译镜像,指定名称为:s3cmd,运行容器

docker run s3cmd 

默认会运行 s3cmd 并带上 --help 参数,即:显示该命令的帮助

运行下面命令:

docker run s3cmd ls s3://mybucket 

ls s3://mybucket 会覆盖默认可选参数 --help

如果需要覆盖 ENTRYPOINT,需要使用 --entrypoint 参数

VOLUME

暴露镜像中可变和用户可修改的数据,比如:存储文件、配置文件,比如:

VOLUME /data 

设置的目录会在容器运行时自动挂载为匿名卷,如果没有设置,就会写入容器存储层

USER

如果不需要使用 sudo ,可以通过 USER 切换到非 root 用户,例如:

RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres 

WORKDIR

WORKDIR 指令可以来指定工作目录,不存在会自动创建
Dockerfile 不同于 Shell,下面的命令其实是不同的层,第一条的 cd 不会影响第二条命令,最终运行结束会导致在 /app 下找不到 world.txt 文件

RUN cd /app RUN echo "hello" > world.txt 

应该使用:

WORKDIR /app RUN echo "hello" > world.txt 

参考资料

使用 Dockerfile 定制镜像

利用构建缓存机制缩短Docker镜像构建时间

Dockerfile: ENTRYPOINT和CMD的区别