五、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 。
- 如果 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 /app COPY pom.xml . COPY src . RUN mvn clean package -Dmaven .test .skip = true # /app 下面有 target RUN pwd && ls -l RUN cp /app/target/*.jar /app.jar RUN ls -l ### 以上第一阶段结束,我们得到了一个 app.jar ## 只要一个 JRE # FROM openjdk:8-jre-alpine FROM openjdk:8u282-slim RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone LABEL 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