【云原生 | docker篇】深入dockerfile【与云原生的故事】-4008云顶国际网站

发表于 2022/05/01 00:56:36 2022/05/01
【摘要】 dockerfile由一行行命令语句组成,并且支持以#开头的注释行。 一般而言,dockerfile可以分为四部分:基础镜像信息、维护者信息、镜像操作指令、启动时执行指令。

前言

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

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


一、命令说明

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

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

  • 基础镜像信息
  • 维护者信息
  • 镜像操作指令
  • 启动时执行指令 
指令 说明
from 指定基础镜像
maintainer 指定维护者信息,已经过时,可以使用labelmaintainer-xxx来替代
run 运行命令v
cmd 指定启动容器时默认的命令v
entrypoint 指定镜像的默认入口.运行命令v
expose 声明镜像内服务监听的端口v
env 指定环境变量,可以在docker run的时候使用-e改变v;会被固化到image的config里面
add 复制指定的src路径下的内容到容器中的dest路径下,src可以为url会自动下载,可以为tar文件,会自动解压
copy 复制本地主机的src路径下的内容到镜像中的dest路径下,但不会自动解压等
label 指定生成镜像的元数据标签信息
volume 创建数据卷挂载点
user 指定运行容器时的用户名或uid
workdir 配置工作目录,为后续的run、cmd、entrypoint指令配置工作目录
arg 指定镜像内使用的参数(如版本号信息等),可以在build的时候,使用--build-args改变v
obbuild 配置当创建的镜像作为其他镜像的基础镜像是,所指定的创建操作指令
stopsignal 容器退出的信号值
healthcheck 健康检查
shell 指定使用shell时的默认shel类型


 二、from 

from 指定基础镜像,最好挑一些aplineslim之类的基础小镜像
scratch镜像是一个空镜像,常用于多阶段构建
如何确定我需要什么要的基础镜像?
  • java应用当然是java基础镜像(springboot应用)或者tomcat基础镜像(war应用)
  • js模块化应用一般用nodejs基础镜像
  • 其他各种语言用自己的服务器或者基础环境镜像,如pythongolangjavaphp


三、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 ( 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 的形式都可以输出变量信息


总结:什么是shellexec形式



五、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、cmdentrypoint提供默认参数

  • 如果使用cmdentrypoint指令提供默认参数,则cmdentrypoint指令均应使用json数组格式指定。


5.4、组合最终效果

无entrypoint entrypointexec_entryp1_entry entrypoint["exec_entry"pl_entry"]
无cmd 错误不允许的写法;容器没有启动命令 /bin/sh-cexec_entryp1_entry exec_entry pl_entry
cmd[exec_cmd","pl_cmd"] exec_cmd p1_cmd /bin/sh-cexec_entryp1l_entry exec_entry p1_entryexec_cmd p1_cmd
cmd["p1_cmd","p2_cmd"] p1_cmd p2_cmd /bin/sh-cexec_entrypl_entry exec_entryp1_entry p1_cmdp2_cmd
cmdexec_cmdp1_cmd /bin/sh-c exec_cmdp1_cmd /bin/sh-cexec_entrypl_entry exec_entry p1_entry/bin/sh-c exec_cmdp1_cmd
这条竖线,总是以entrypoint的为准 这条竖线,entrypoint和cmd共同作用


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


六、argenv

6.1arg

  • 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值可以生效
  • envimage阶段就会被解析并持久化(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


七、addcopy

7.1copy

copy的两种写法
copy [--chown=:] ...
copy [--chown=:] ["",... ""]
  • --chown功能仅在用于构建linux容器的dockerfiles上受支持,而在windows容器上不起作用
  • copy指令从 src 复制新文件或目录,并将它们添加到容器的文件系统中,路径为 dest
  • 可以指定多个 src 资源,但是文件和目录的路径将被解释为相对于构建上下文的源。
  • 每个 src 都可以包含通配符,并且匹配将使用gofilepath.match规则进行。
copy hom* /mydir/ #当前上下文,以home开始的所有资源
copy hom?.txt /mydir/ # ?匹配单个字符
copy test.txt relativedir/ # 目标路径如果设置为相对路径,则相对与 workdir 开始

# “test.txt” 添加到 /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 是目录,则将复制目录的整个内容,包括文件系统元数据。


八、workdirvolume

8.1、workdir

  • workdir指令为dockerfile中跟随它的所有 runcmdentrypointcopyadd 指令设置工作目录。 如果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 [:]
user指令设置运行映像时要使用的用户名(或uid)以及可选的用户组(或gid),以及dockerfile中user后面所有runcmdentrypoint指令。


十、expose

  • expose指令通知docker容器在运行时在指定的网络端口上进行侦听。 可以指定端口是侦听tcp还是udp,如果未指定协议,则默认值为tcp
  • expose指令实际上不会发布端口。 它充当构建映像的人员和运行容器的人员之间的一种文档,即有关打算发布哪些端口的信息。 要在运行容器时实际发布端口,请在docker run上使用-p标志发布并映射一个或多个端口,或使用-p标志发布所有公开的端口并将其映射到高阶端口。


expose [/...]
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;第二阶段并不会有第一阶段哪些没用的层
基础镜像没有 jmapjdk springboot-actutorjdk
from openjdk:8-jre-alpine
label maintainer="534096094@qq.com"

# 从上一个阶段复制内容
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" ]


aliyun
nexus snapshot repository
https://maven.aliyun.com/repository/public
default
true
true
aliyun
nexus snapshot repository
https://maven.aliyun.com/repository/public
default
true
true
######小细节
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
可以让镜像时间同步。

## 容器同步系统时间 cstchina 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="534096094@qq.com"
# 把上一个阶段的东西复制过来
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="534096094@qq.com"
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


【与云原生的故事】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/345260

【4008云顶国际集团的版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。