写点什么

云原生(五) | Docker 篇之深入 Dockerfile

作者:Lansonli
  • 2022 年 7 月 17 日
  • 本文字数:5392 字

    阅读完需:约 18 分钟

 深入 Dockerfile



前言

博主语录:一文精讲一个知识点,多了你记不住,一句废话都没有

经典语录:一厢情愿,就得愿赌服輸


一、命令说明

Dockerfile 由一行行命令语句组成,并且支持以 #开头的注释行。

一般而言,Dockerfile 可以分为四部分

  • 基础镜像信息

  • 维护者信息

  • 镜像操作指令

  • 启动时执行指令 



 二、FROM 

FROM 指定基础镜像,最好挑一些 apline,slim 之类的基础小镜像

scratch 镜像是一个空镜像,常用于多阶段构建

如何确定我需要什么要的基础镜像?

  • Java 应用当然是 java 基础镜像(SpringBoot 应用)或者 Tomcat 基础镜像(War 应用)

  • JS 模块化应用一般用 nodejs 基础镜像

  • 其他各种语言用自己的服务器或者基础环境镜像,如 python、golang、java、php 等


三、LABEL

标注镜像的一些说明信息。

LABEL multi.label1="value1" multi.label2="value2" other="value3"LABEL multi.label1="value1" \multi.label2="value2" \ other="value3"
复制代码


四、RUN  

  • RUN 指令在当前镜像层顶部的新层执行任何命令,并提交结果,生成新的镜像层。

  • 生成的提交映像将用于 Dockerfile 中的下一步。 分层运行 RUN 指令并生成提交符合 Docker 的核心概念,就像源代码控制一样。

  • exec 形式可以避免破坏 shell 字符串,并使用不包含指定 shell 可执行文件的基本映像运行 RUN 命令。 可以使用 SHELL 命令更改 shell 形式的默认 shell。 在 shell 形式中,您可以使用\(反斜杠)将一条 RUN 指令继续到下一行。


RUN <command> ( shell 形式, /bin/sh -c 的方式运行,避免破坏 shell 字符串)

RUN ["executable", "param1", "param2"] ( exec 形式)

RUN /bin/bash -c 'source $HOME/.bashrc; \

echo $HOME'

#上面等于下面这种写法

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

RUN ["/bin/bash", "-c", "echo hello"]

# 测试案例

FROM alpine

LABEL maintainer=leifengyang xx=aa

ENV msg='hello atguigu itdachang'

RUN echo $msg

RUN ["echo","$msg"]

RUN /bin/sh -c 'echo $msg'

RUN ["/bin/sh","-c","echo $msg"]

CMD sleep 10000


#总结; 由于[]不是 shell 形式,所以不能输出变量信息,而是输出 $msg。其他任何/bin/sh -c 的形式都可以输出变量信息


总结:什么是 shell 和 exec 形式


五、CMD 和 ENTRYPOINT

5.1、都可以作为容器启动入口

CMD 的三种写法:

  • CMD ["executable","param1","param2"] ( exec 方式, 首选方式)

  • CMD ["param1","param2"] (为 ENTRYPOINT 提供默认参数)

  • CMD command param1 param2 ( shell 形式)

ENTRYPOINT 的两种写法:

  • ENTRYPOINT ["executable", "param1", "param2"] ( exec 方式, 首选方式)

  • ENTRYPOINT command param1 param2 (shell 形式)


# 一个示例

FROM alpine

LABEL maintainer=leifengyang

CMD ["1111"]

CMD ["2222"]

ENTRYPOINT ["echo"]

#构建出如上镜像后测试

docker run xxxx:效果 echo 1111


5.2、只能有一个 CMD

  • Dockerfile 中只能有一条 CMD 指令。 如果您列出多个 CMD,则只有最后一个 CMD 才会生效。

  • CMD 的主要目的是为执行中的容器提供默认值。 这些默认值可以包含可执行文件,也可以省略可执行文件,在这种情况下,您还必须指定 ENTRYPOINT 指令。


5.3、CMD 为 ENTRYPOINT 提供默认参数

  • 如果使用 CMD 为 ENTRYPOINT 指令提供默认参数,则 CMD 和 ENTRYPOINT 指令均应使用 JSON 数组格式指定。


5.4、组合最终效果


5.5、docker run 启动参数会覆盖 CMD 内容 


# 一个示例

FROM alpine

LABEL maintainer=leifengyang

CMD ["1111"]

ENTRYPOINT ["echo"]

#构建出如上镜像后测试

docker run xxxx:什么都不传则 echo 1111

docker run xxx arg1:传入 arg1 则 echo arg1


六、ARG 和 ENV

6.1、ARG

  • ARG 指令定义了一个变量,用户可以在构建时使用--build-arg = 传递,docker build 命令会将其传递给构建器。

  • --build-arg 指定参数会覆盖 Dockerfile 中指定的同名参数

  • 如果用户指定了 未在 Dockerfile 中定义的构建参数 ,则构建会输出 警告

  • ARG 只在构建期有效,运行期无效

  • 不建议使用构建时变量来传递诸如 github 密钥,用户凭据等机密。因为构建时变量值使用 docker history 是可见的。

  • ARG 变量定义从 Dockerfile 中定义的行开始生效。

  • 使用 ENV 指令定义的环境变量始终会覆盖同名的 ARG 指令。


6.2、ENV

  • 在构建阶段中所有后续指令的环境中使用,并且在许多情况下也可以内联替换。

  • 引号和反斜杠可用于在值中包含空格。

  • ENV 可以使用 key value 的写法,但是这种不建议使用了,后续版本可能会删除


ENV MY_MSG hello

ENV MY_NAME="John Doe"

ENV MY_DOG=Rex\ The\ Dog

ENV MY_CAT=fluffy

#多行写法如下

ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \

MY_CAT=fluffy

  • docker run --env 可以修改这些值

  • 容器运行时 ENV 值可以生效

  • ENV 在 image 阶段就会被解析并持久化(docker inspect image 查看),参照下面示例。

 FROM alpine

ENV arg=1111111

ENV runcmd=$arg

RUN echo $runcmd

CMD echo $runcmd

#ENV 的固化问题: 改变 arg,会不会改变 echo 的值,会改变哪些值,如何修改这些值?


6.3、综合测试示例 


FROM alpine

ARG arg1=22222

ENV arg2=1111111

ENV runcmd=$arg1

RUN echo $arg1 $arg2 $runcmd

CMD echo $arg1 $arg2 $runcmd


七、ADD 和 COPY

7.1、COPY

COPY 的两种写法

COPY [--chown=<user>:<group>] <src>... <dest>

COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

  • --chown 功能仅在用于构建 Linux 容器的 Dockerfiles 上受支持,而在 Windows 容器上不起作用

  • COPY 指令从 src 复制新文件或目录,并将它们添加到容器的文件系统中,路径为 dest

  • 可以指定多个 src 资源,但是文件和目录的路径将被解释为相对于构建上下文的源。

  • 每个 src 都可以包含通配符,并且匹配将使用 Go 的 filepath.Match 规则进行。

COPY hom* /mydir/ #当前上下文,以 home 开始的所有资源

COPY hom?.txt /mydir/ # ?匹配单个字符

COPY test.txt relativeDir/ # 目标路径如果设置为相对路径,则相对与 WORKDIR 开始


# 把 “test.txt” 添加到 <WORKDIR>/relativeDir/

COPY test.txt /absoluteDir/ #也可以使用绝对路径,复制到容器指定位置


#所有复制的新文件都是 uid(0)/gid(0)的用户,可以使用--chown 改变

COPY --chown=55:mygroup files* /somedir/

COPY --chown=bin files* /somedir/

COPY --chown=1 files* /somedir/

COPY --chown=10:11 files* /somedir/


7.2、ADD

同 COPY 用法,不过 ADD 拥有自动下载远程文件和解压的功能。

注意:

  • src 路径必须在构建的上下文中; 不能使用 ../something /something 这种方式,因为 docker 构建的第一步是将上下文目录(和子目录)发送到 docker 守护程序。

  • 如果 src 是 URL,并且 dest 不以斜杠结尾,则从 URL 下载文件并将其复制到 dest

  1. 如果 dest 以斜杠结尾,将自动推断出 url 的名字(保留最后一部分),保存到 dest

  • 如果 src 是目录,则将复制目录的整个内容,包括文件系统元数据。


八、WORKDIR 和 VOLUME

8.1、WORKDIR

  • WORKDIR 指令为 Dockerfile 中跟随它的所有 RUN,CMD,ENTRYPOINT,COPY,ADD 指令设置工作目录。 如果 WORKDIR 不存在,即使以后的 Dockerfile 指令中未使用它也将被创建。

  • WORKDIR 指令可在 Dockerfile 中多次使用。 如果提供了相对路径,则它将相对于上一个 WORKDIR 指令的路径。 例如:


WORKDIR /a

WORKDIR b

WORKDIR c

RUN pwd

#结果 /a/b/c

  • 也可以用到环境变量


ENV DIRPATH=/path

WORKDIR $DIRPATH/$DIRNAME

RUN pwd

#结果 /path/$DIRNAME


8.2、VOLUME

作用:把容器的某些文件夹映射到主机外部

写法:

VOLUME ["/var/log/"] #可以是 JSON 数组

VOLUME /var/log #可以直接写

VOLUME /var/log /var/db #可以空格分割多个

注意:

用 VOLUME 声明了卷,那么以后对于卷内容的修改会被丢弃,所以, 一定在 volume 声明之前修改内容


九、USER

写法:

USER <user>[:<group>]

USER <UID>[:<GID>]

USER 指令设置运行映像时要使用的用户名(或 UID)以及可选的用户组(或 GID),以及 Dockerfile 中 USER 后面所有 RUN,CMD 和 ENTRYPOINT 指令。


十、EXPOSE

  • EXPOSE 指令通知 Docker 容器在运行时在指定的网络端口上进行侦听。 可以指定端口是侦听 TCP 还是 UDP,如果未指定协议,则默认值为 TCP。

  • EXPOSE 指令实际上不会发布端口。 它充当构建映像的人员和运行容器的人员之间的一种文档,即有关打算发布哪些端口的信息。 要在运行容器时实际发布端口,请在 docker run 上使用-p 标志发布并映射一个或多个端口,或使用-P 标志发布所有公开的端口并将其映射到高阶端口。


EXPOSE <port> [<port>/<protocol>...]

EXPOSE [80,443]

EXPOSE 80/tcp

EXPOSE 80/udp


十一、multi-stage builds

多阶段构建

11.1、使用

https://docs.docker.com/develop/develop-images/multistage-build/

解决:如何让一个镜像变得更小; 多阶段构建的典型示例

### 我们如何打包一个 Java 镜像

FROM maven

WORKDIR /app

COPY . .

RUN mvn clean package

COPY /app/target/*.jar /app/app.jar

ENTRYPOINT java -jar app.jar


## 这样的镜像有多大?

## 我们最小做到多大?


11.2、生产示例

# 以下所有前提 保证 Dockerfile 和项目在同一个文件夹

# 第一阶段:环境构建; 用这个也可以

FROM maven:3.5.0-jdk-8-alpine AS builder

WORKDIR /app

ADD ./ /app

RUN mvn clean package -Dmaven.test.skip=true


# 第二阶段,最小运行时环境,只需要 jre;第二阶段并不会有第一阶段哪些没用的层

# 基础镜像没有 jmap; jdk springboot-actutor(jdk)

FROM openjdk:8-jre-alpine

LABEL maintainer="lanson"


# 从上一个阶段复制内容

COPY --from=builder /app/target/*.jar /app.jar


# 修改时区

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo

'Asia/Shanghai' >/etc/timezone && touch /app.jar

ENV JAVA_OPTS=""

ENV PARAMS=""


# 运行 jar 包

ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom

$JAVA_OPTS -jar /app.jar $PARAMS" ]


<!--为了加速下载需要在 pom 文件中复制如下 -->

<repositories>

<repository>

<id>aliyun</id>

<name>Nexus Snapshot Repository</name>

<url>https://maven.aliyun.com/repository/public</url>

<layout>default</layout>

<releases>

<enabled>true</enabled>

</releases>

<!--snapshots 默认是关闭的,需要开启 -->

<snapshots>

<enabled>true</enabled>

</snapshots>

</repository>

</repositories>

<pluginRepositories>

<pluginRepository>

<id>aliyun</id>

<name>Nexus Snapshot Repository</name>

<url>https://maven.aliyun.com/repository/public</url>

<layout>default</layout>

<releases>

<enabled>true</enabled>

</releases>

<snapshots>

<enabled>true</enabled>

</snapshots>

</pluginRepository>

</pluginRepositories>

######小细节

RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo

'Asia/Shanghai' >/etc/timezone

或者

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo

'Asia/Shanghai' >/etc/timezone

可以让镜像时间同步。


## 容器同步系统时间 CST(China Shanghai Timezone)

-v /etc/localtime:/etc/localtime:ro


#已经不同步的如何同步?

docker cp /etc/localtime 容器 id:/etc/


docker build --build-arg url="git address" -t demo:test . :自动拉代码并构建镜像
复制代码


FROM maven:3.6.1-jdk-8-alpine AS buildapp#第二阶段,把克隆到的项目源码拿过来# COPY --from=gitclone * /app/WORKDIR /appCOPY pom.xml .COPY src .RUN mvn clean package -Dmaven.test.skip=true# /app 下面有 targetRUN pwd && ls -lRUN cp /app/target/*.jar /app.jarRUN ls -l### 以上第一阶段结束,我们得到了一个 app.jar## 只要一个JRE# FROM openjdk:8-jre-alpineFROM openjdk:8u282-slimRUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo'Asia/Shanghai' >/etc/timezoneLABEL maintainer="lanson"# 把上一个阶段的东西复制过来COPY --from=buildapp /app.jar /app.jar# docker run -e JAVA_OPTS="-Xmx512m -Xms33 -" -e PARAMS="--spring.profiles=dev --server.port=8080" -jar /app/app.jar# 启动java的命令ENV JAVA_OPTS=""ENV PARAMS=""ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom$JAVA_OPTS -jar /app.jar $PARAMS" ]
复制代码


自己 写一个多阶段构建

  • 1、自动从 git 下载指定的项目

  • 2、把项目自动打包生成镜像

  • 3、我们只需要运行镜像即可


十二、Images 瘦身实践 

  • 选择最小的基础镜像

  • 合并 RUN 环节的所有指令,少生成一些层

  • RUN 期间可能安装其他程序会生成临时缓存,要自行删除。如:


# 开发期间,逐层验证正确的

RUN xxx

RUN xxx

RUN aaa \

aaa \

vvv \


# 生产环境

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

bzr \

cvs \

git \

mercurial \

subversion \

&& rm -rf /var/lib/apt/lists/*

  • 使用 .dockerignore 文件,排除上下文中无需参与构建的资源

  • 使用多阶段构建

  • 合理使用构建缓存加速构建。[--no-cache]

学习更多 Dockerfile 的写法:https://github.com/docker-library/


十三、springboot java 最终写法

FROM openjdk:8-jre-alpine
LABEL maintainer="lanson"
COPY target/*.jar /app.jar
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo
'Asia/Shanghai' >/etc/timezone && touch /app.jar
ENV JAVA_OPTS=""
ENV PARAMS=""
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom
$JAVA_OPTS -jar /app.jar $PARAMS" ]
# 运行命令 docker run -e JAVA_OPTS="-Xmx512m -Xms33 -" -e PARAMS="--spring.profiles=dev --server.port=8080" -jar /app/app.jar
复制代码


发布于: 刚刚阅读数: 3
用户头像

Lansonli

关注

微信公众号:三帮大数据 2022.07.12 加入

CSDN大数据领域博客专家,华为云享专家、阿里云专家博主、腾云先锋(TDP)核心成员、51CTO专家博主,全网六万多粉丝,知名互联网公司大数据高级开发工程师

评论

发布
暂无评论
云原生(五) | Docker篇之深入Dockerfile_云原生_Lansonli_InfoQ写作社区