乐趣区

关于docker:看完这篇再也不用担心不会写dockerfile了

Dockerfile 是 Docker 用来构建镜像的文本文件, 包含自定义的指令和格局。能够通过 docker build 命令从 Dockerfile 中构建镜像。用户能够通过对立的语法命令来依据需要进行配置,通过这份对立的配置文件,在不同的文件上进行散发,须要应用时就能够依据配置文件进行自动化构建,这解决了开发人员构建镜像的简单过程。

Dockerfile 的应用

Dockerfile 形容了组装对象的步骤,其中每条指令都是独自运行的。除了 FROM 指令,其余每条命令都会在上一条指令所生成镜像的根底上执行,执行完后会生成一个新的镜像层,新的镜像层笼罩在原来的镜像之上从而造成了新的镜像。Dockerfile 所生成的最终镜像就是在根底镜像下面叠加一层层的镜像层组建的。

Dockerfile 指令

Dockerfile 的根本格局如下:

# Comment
INSTRUCTION arguments

在 Dockerfile 中, 指令 (INSTRUCTION) 不辨别大小写,然而为了与参数辨别,举荐大写。
Docker 会程序执行 Dockerfile 中的指令,第一条指令必须是 FROM 指令,它用于指定构建镜像的根底镜像。在 Dockerfile 中以 #结尾的行是正文,而在其余地位呈现的# 会被当成参数。

Dockerfile 中的指令有 FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOING、VOLUME、USER、WORKDIR、ONBUILD, 谬误的指令会被疏忽。上面将具体解说一些重要的 Docker 指令。

FROM

格局: FROM <image> 或者 FROM <image>:<tag>

FROM 指令的性能是为前面的指令提供根底镜像, 因而 Dockerfile 必须以 FROM 指令作为第一条非正文指令。从公共镜像库中拉取镜像很容易, 根底镜像能够抉择任何无效的镜像。
在一个 Dockerfile 中 FROM 指令能够呈现屡次, 这样会构建多个镜像。tag 的默认值是 latest, 如果参数 image 或者 tag 指定的镜像不存在,则返回谬误。

ENV

格局: ENV <key> <value> 或者 ENV <key>=<value> ...

ENV 指令能够为镜像创立进去的容器申明环境变量。并且在 Dockerfile 中,ENV 指令申明的环境变量会被前面的特定指令 (即 ENV、ADD、COPY、WORKDIR、EXPOSE、VOLUME、USER) 解释应用。

其余指令应用环境变量时,应用格局为 $variable_name 或者 ${variable_name}。如果在变量背后增加斜杠 \ 能够本义。如\\$foo 或者 \\${foo} 将会被转换为 $foo${foo}, 而不是环境变量所保留的值。另外,ONBUILD 指令不反对环境替换。

COPY

格局: COPY <src> <dest>

COPY 指令复制 <src> 所指向的文件或目录, 将它增加到新镜像中, 复制的文件或目录在镜像中的门路是 <dest><src> 所指定的源能够有多个, 但必须是上下文根目录中的相对路径。
不能只用形如 COPY ../something /something这样的指令。此外,<src>能够应用通配符指向所有匹配通配符的文件或目录,例如,COPY home* /mydir/ 示意增加所有以 ”hom” 结尾的文件到目录 /mydir/ 中。

<dest>能够是文件或目录,但必须是指标镜像中的绝对路径或者绝对于 WORKDIR 的相对路径 (WORKDIR 即 Dockerfile 中 WORKDIR 指令指定的门路, 用来为其余指令设置工作目录)。
<dest>以反斜杠 / 结尾则其指向的是目录;否则指向文件。<src>同理。若 <dest> 是一个文件,则 <src> 的内容会被写到 <dest> 中;否则 <src> 指向的文件或目录中的内容会被复制增加到 <dest> 目录中。
<src> 指定多个源时,<dest>必须是目录。如果 <dest> 不存在,则门路中不存在的目录会被创立。

ADD

格局:ADD <src> <dest>

ADD 与 COPY 指令在性能上很类似,都反对复制本地文件到镜像的性能,但 ADD 指令还反对其余性能。<src>能够是指向网络文件的 URL, 此时若 <dest> 指向一个目录,则 URL 必须是齐全门路,这样能够取得网络文件的文件名 filename,该文件会被复制增加到 <dest>/<filename>
比方 ADD http://example.com/config.pro… / 会创立文件 /config.property。

<src>还能够指向一个本地压缩归档文件,该文件会在复制到容器时会被解压提取,如 ADD sxample.tar.xz /。然而若 URL 中的文件为归档文件则不会被解压提取。

ADD 和 COPY 指令尽管性能类似,但个别举荐应用 COPY, 因为 COPY 只反对本地文件,相比 ADD 而言,它更加通明。

EXPOSE

格局: EXPOSE <port> [<port>/<protocol>...]

EXPOSE 指令告诉 Docker 该容器在运行时侦听指定的网络端口。能够指定端口是侦听 TCP 还是 UDP,如果未指定协定,则默认值为 TCP。
这个指令仅仅是申明容器打算应用什么端口而已,并不会主动在宿主机进行端口映射, 能够在运行的时候通过 docker - p 指定。

EXPOSE 80/tcp
EXPOSE 80/udp

USER

格局: USER <user>[:<group] 或者 USER <UID>[:<GID>]

USER 指令设置了 user name 和 user group(可选)。在它之后的 RUN,CMD 以及 ENTRYPOINT 指令都会以设置的 user 来执行。

WORKDIR

格局: WORKDIR /path/to/workdir

WORKDIR 指令设置工作目录,它之后的 RUN、CMD、ENTRYPOINT、COPY 以及 ADD 指令都会在这个工作目录下运行。如果这个工作目录不存在,则会主动创立一个。
WORKDIR 指令可在 Dockerfile 中屡次应用。如果提供了相对路径,则它将绝对于上一个 WORKDIR 指令的门路。例如

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

输入后果是 /a/b/c

RUN

格局 1:RUN <command> (shell 格局)
格局 2:RUN ["executable", "param1", "param2"] (exec 格局,举荐应用)

RUN 指令会在前一条命令创立出的镜像的根底上创立一个容器,并在容器中运行命令,在命令完结运行后提交容器为新镜像,新镜像被 Dockerfile 中的下一条指令应用。

RUN 指令的两种格局表示命令在容器中的两种运行形式。当应用 shell 格局时,命令通过 /bin/sh - c 运行。
当应用 exec 格局时,命令是间接运行的,容器不调用 shell 程序,即容器中没有 shell 程序。
exec 格局中的参数会被当成 JSON 数组被 Docker 解析,故必须应用双引号而不能应用单引号。因为 exec 格局不会在 shell 中执行,所以环境变量的参数不会被替换。

比方执行 RUN ["echo", "$HOME"] 指令时,$HOME 不会做变量替换。如果心愿运行 shell 程序,指令能够写成 RUN ["/bin/bash", "-c", "echo", "$HOME"]

CMD

CMD 指令有 3 种格局。

格局 1:CMD <command> (shell 格局)
格局 2:CMD ["executable", "param1", "param2"] (exec 格局,举荐应用)
格局 3:CMD ["param1", "param2"] (为 ENTRYPOINT 指令提供参数)

CMD 指令提供容器运行时的默认值,这些默认值能够是一条指令,也能够是一些参数。一个 Dockerfile 中能够有多条 CMD 指令,但只有最初一条 CMD 指令无效。
CMD [“param1”, “param2”]格局是在 CMD 指令和 ENTRYPOINT 指令配合时应用的,CMD 指令中的参数会增加到 ENTRYPOING 指令中. 应用 shell 和 exec 格局时,命令在容器中的运行形式与 RUN 指令雷同。

不同之处在于,RUN 指令在构建镜像时执行命令,并生成新的镜像;CMD 指令在构建镜像时并不执行任何命令,而是在容器启动时默认将 CMD 指令作为第一条执行的命令。如果用户在命令行界面运行 docker run 命令时指定了命令参数,则会笼罩 CMD 指令中的命令。

ENTRYPOINT

ENTRYPOINT 指令有两种格局。

格局 1:ENTRYPOINT <command> (shell 格局)
格局 2:ENTRYPOINT ["executable", "param1", "param2"] (exec 格局,举荐格局)

ENTRYPOINT 指令和 CMD 指令相似,都能够让容器在每次启动时执行雷同的命令,但它们之间又有不同。一个 Dockerfile 中能够有多条 ENTRYPOINT 指令,但只有最初一条 ENTRYPOINT 指令无效。

当应用 Shell 格局时,ENTRYPOINT 指令会疏忽任何 CMD 指令和 docker run 命令的参数,并且会运行在 bin/sh - c 中。这意味着 ENTRYPOINT 指令过程为 bin/sh - c 的子过程, 过程在容器中的 PID 将不是 1,且不能承受 Unix 信号。即当应用 docker stop <container> 命令时,命令过程接管不到 SIGTERM 信号。

举荐应用 exec 格局,应用此格局时,docker run 传入的命令参数会笼罩 CMD 指令的内容并且附加到 ENTRYPOINT 指令的参数中。从 ENTRYPOINT 的应用中能够看出,CMD 能够是参数,也能够是指令,而 ENTRYPOINT 只能是命令;另外,docker run 命令提供的运行命令参数能够笼罩 CMD, 但不能笼罩 ENTRYPOINT。

Dockerfile 实际心得

应用标签

给镜像打上标签,有利于帮忙理解进镜像性能

审慎抉择根底镜像

抉择根底镜像时,尽量抉择以后官网镜像库的肩宽,不同镜像的大小不同,目前 Linux 镜像大小由如下关系:

busybox < debian < centos < ubuntu

同时在构建本人的 Docker 镜像时, 只装置和更新必须应用的包。此外相比 Ubuntu 镜像,更举荐应用 Debian 镜像,因为它十分轻量级(目前其大小是在 100MB 以下), 并且依然是一个残缺的公布版本。

充分利用缓存

Docker daemon 会程序执行 Dockerfile 中的指令,而且一旦缓存生效,后续命令将不能应用缓存。为了无效地利用缓存,须要保障指令的连续性,尽量将所有 Dockerfile 文件雷同的局部都放在后面,而将不同的局部放到前面。

正确应用 ADD 与 COPY 命令

当在 Dockerfile 中的不同局部须要用到不同的文件时,不要一次性地将这些文件都增加到镜像中去,而是在须要时增加,这样也有利于反复利用 docker 缓存。
另外思考到镜像大小问题,应用 ADD 指令去获取近程 URL 中的压缩包不是举荐的做法。应该应用 RUN wget 或 RUN curl 代替。这样能够删除解压后不在须要的文件,并且不须要在镜像中在增加一层。

错误做法:

ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

正确的做法:

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

RUN 指令

在应用较长的 RUN 指令时能够应用反斜杠 \ 分隔多行。大部分应用 RUN 指令的常见是运行 apt-wget 命令,在该场景下请留神以下几点。

  1. 不要在一行中独自应用指令 RUN apt-get update。当软件源更新后,这样做会引起缓存问题,导致 RUN apt-get install 指令运行失败。所以,RUN apt-get update 和 RUN apt-get install 应该写在同一行。比方 RUN apt-get update && apt-get install -y package-1 package-2 package-3
  2. 防止应用指令 RUN apt-get upgrade 和 RUN apt-get dist-upgrade。因为在一个无特权的容器中,一些必要的包会更新失败。如果须要更新一个包(如 package-1),间接应用命令 RUN apt-get install -y package-1。

CMD 和 ENTRYPOINT 命令

CMD 和 ENTRYPOINT 命令指定是了容器运行的默认命令,举荐二者联合应用。应用 exec 格局的 ENTRYPOINT 指令设置固定的默认命令和参数,而后应用 CMD 指令设置可变的参数。

比方上面这个例子:

FROM busybox
WORKDIR /app
COPY run.sh /app
RUN chmod +x run.sh
ENTRYPOINT ["/app/run.sh"]
CMD ["param1"]

run.sh 内容如下:

#!/bin/sh
echo "$@"

运行后输入后果为 param1, Dockerfile 中 CMD 和 ENTRYPOINT 的程序不重要(CMD 写在 ENTRYPOINT 前后都能够)。

当在 windows 零碎下 build dockerfile 你可能会遇到这个问题

standard_init_linux.go:207: exec user process caused "no such file or directory"

这是因为 sh 文件的 fileformat 是 dos, 这里须要批改为 unix, 不须要下载额定的工具,个别咱们机器上装置了 git 会自带 git bash, 进入 git bash, 应用 vi 编辑,在命令行模式下批改(:set ff=unix)。

不要再 Dockerfile 中做端口映射

应用 Dockerfile 的 EXPOSE 指令,尽管能够将容器端口映射在主机端口上,但会毁坏 Docker 的可移植性,且这样的镜像在一台主机上只能启动一个容器。所以端口映射应在 docker run 命令中用 -p 参数指定。

# 不要再 Dockerfile 中做如下映射
EXPOSE 80:8080

# 仅裸露 80 端口, 须要另做映射
EXPOSE 80

实际 Dockerfile 的写法

Java 服务的 DockerFile

FROM openjdk:8-jre-alpine
ENV spring_profiles_active=dev
ENV env_java_debug_enabled=false
EXPOSE 8080
WORKDIR /app
ADD target/smcp-web.jar /app/target/smcp-web.jar
ADD run.sh /app
ENTRYPOINT ./run.sh

能够看到根底镜像是 openjdk, 而后设置了两个环境变量, 服务拜访端口是 9090(意味着 springboot 利用中指定了 server.port=8080), 设置了工作目录是 /app。通过 ENTRYPOINT 设定了启动镜像时要启动的命令(./run.sh)。这个脚本中的内容如下:

#!/bin/sh
# Set debug options if required
if [x"${env_java_debug_enabled}" != x ] && ["${env_java_debug_enabled}" != "false" ]; then
    java_debug_args="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
fi

# ex: env_jvm_flags="-Xmx1200m -XX:MaxRAM=1500m" for production
java $java_debug_args $env_jvm_flags -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar target/smcp-web.jar

如果咱们要指定 jvm 的一些参数, 能够通过在环境变量中设置 env_jvm_flags 来指定。

Maven Dockerfile

maven 的 Dockerfile 也写的很好,这里我发上来也给大家参考下

FROM openjdk:8-jdk

ARG MAVEN_VERSION=3.6.3
ARG USER_HOME_DIR="/root"
ARG SHA=c35a1803a6e70a126e80b2b3ae33eed961f83ed74d18fcd16909b2d44d7dada3203f1ffe726c17ef8dcca2dcaa9fca676987befeadc9b9f759967a8cb77181c0
ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries

RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
  && curl -fsSL -o /tmp/apache-maven.tar.gz ${BASE_URL}/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
  && echo "${SHA}  /tmp/apache-maven.tar.gz" | sha512sum -c - \
  && tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
  && rm -f /tmp/apache-maven.tar.gz \
  && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn

ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2"

COPY mvn-entrypoint.sh /usr/local/bin/mvn-entrypoint.sh
COPY settings-docker.xml /usr/share/maven/ref/

ENTRYPOINT ["/usr/local/bin/mvn-entrypoint.sh"]
CMD ["mvn"]

能够看到它是基于 openjdk 这个根底镜像来创立的,先去下载 maven 的包,而后进行了装置。而后又设置了 MAVEN_HOME 和 MAVEN_CONFIG 这两个环境变量,最初通过 mvn-entrypoing.sh 来进行了启动。

前端服务的两阶段构建

我有一个前端服务,目录构造如下:

$ ls frontend/
myaccount/  resources/  third_party/

myaccount 目录下是搁置的 js,vue 等,resources 搁置的是 css,images 等。third_party 放的是第三方利用。

这里采纳了两阶段构建,即采纳上一阶段的构建后果作为下一阶段的构建数据

FROM node:alpine as builder
WORKDIR '/build'
COPY myaccount ./myaccount
COPY resources ./resources
COPY third_party ./third_party

WORKDIR '/build/myaccount'

RUN npm install
RUN npm rebuild node-sass
RUN npm run build

RUN ls /build/myaccount/dist

FROM nginx
EXPOSE 80
COPY --from=builder /build/myaccount/dist /usr/share/nginx/html

须要留神结尾的 --from=builder这里和结尾是一唱一和的。

总结

我置信看完 dockerfile 指令, 你看任何一个 dockerfile 应该都没有太大问题, 不记得的命令回来翻一下就行了。如果你感觉还能够,关注下哟。

退出移动版