关于制作Docker镜像?| Dockerfile快速开始
Posted 西京刀客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于制作Docker镜像?| Dockerfile快速开始相关的知识,希望对你有一定的参考价值。
文章目录
一、关于制作Docker镜像?🐋
46 | 如何制作Docker镜像?
参考URL: https://time.geekbang.org/column/article/417216
要落地云原生架构,其中的一个核心点是通过容器来部署我们的应用。如果要使用容器来部署应用,那么制作应用的 Docker 镜像就是我们绕不开的关键一步。
1. Docker 镜像的构建原理和方式
构建一个 Docker 镜像,最常用的有两种:
- 通过docker commit命令,基于一个已存在的容器构建出镜像。
- 编写 Dockerfile 文件,并使用docker build命令来构建镜像。
上面这两种方法中,镜像构建的底层原理是相同的,都是通过下面 3 个步骤来构建镜像:
- 基于原镜像,启动一个 Docker 容器。
- 在容器中进行一些操作,例如执行命令、安装文件等。由这些操作产生的文件变更都会被记录在容器的存储层中。
- 将容器存储层的变更 commit 到新的镜像层中,并添加到原镜像上。
1.1 Docker 镜像的构建方式使用场景和选型
docker commit
这种镜像构建方式通常用在下面两个场景中:
- 构建临时的测试镜像;
- 容器被入侵后,使用docker commit,基于被入侵的容器构建镜像,从而保留现场,方便以后追溯。
除了这两种场景,我不建议你使用docker commit来构建生产现网环境的镜像。
原因如下:
- 使用docker commit构建的镜像包含了编译构建、安装软件,以及程序运行产生的大量无用文件,这会导致镜像体积很大,非常臃肿。
- 使用docker commit构建的镜像会丢失掉所有对该镜像的操作历史,无法还原镜像的构建过程,不利于镜像的维护。
- Dockerfile 的操作流程可以通过
docker image history [镜像名称]
查询,方便开发者查看变更记录。
在实际开发中,使用Dockerfile来构建是最常用,也最标准的镜像构建方法.
使用 Dockerfile 构建镜像,本质上也是通过镜像创建容器,并在容器中执行相应的指令,然后停止容器,提交存储层的文件变更。和用docker commit构建镜像的方式相比,它有三个好处:
- Dockerfile 包含了镜像制作的完整操作流程,其他开发者可以通过 Dockerfile 了解并复现制作过程。
- Dockerfile 中的每一条指令都会创建新的镜像层,这些镜像可以被 Docker Daemnon 缓存。再次制作镜像时,Docker 会尽量复用缓存的镜像层(using cache),而不是重新逐层构建,这样可以节省时间和磁盘空间。
- 把这一切都放到一个 Dockerfile 里,既没有源码泄漏,又不需要用脚本去跨平台编译,还获得了最小的镜像。
在实际生产环境中,标准的做法是通过 Dockerfile 来构建镜像。使用 Dockerfile 构建镜像,就需要你编写 Dockerfile 文件。
二、Dockerfile快速开始
什么是Dockerfile
Dockerfile是一个包含用于组合映像的命令的文本文档。可以使用在命令行中调用任何命令。 Docker通过读取Dockerfile中的指令自动生成映像。
- dockerfile是自定义镜像的一套规则
- dockerfile由多条指令构成,Dockerfile中的每一条指令都会对应于Docker镜像中的每一层。
docker build
命令用于从Dockerfile
构建映像。可以在docker build命令中使用-f标志指向文件系统中任何位置的Dockerfile。
docker build
命令会读取Dockerfile的内容,并将Dockerfile的内容发送给 Docker 引擎,最终 Docker 引擎会解析Dockerfile中的每一条指令,构建出需要的镜像。
Dockerfile的基本结构
Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,’#’ 为 Dockerfile 中的注释。
Dockerfile 常用的指令
Docker以从上到下的顺序运行Dockerfile的指令。为了指定基本映像,第一条指令必须是FROM。一个声明以#字符开头则被视为注释。可以在Docker文件中使用RUN,CMD,FROM,EXPOSE,ENV等指令。
FROM命令
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
- FROM:指定基础镜像,必须为第一个命令
- FROM指令用于指定基础镜像,ARG是唯一可以位于FROM指令之前的指令,一般用于声明基础镜像的版本。
- –platform选项可用在FROM多平台镜像的情况下指定平台。例如,linux/amd64、lunux/arm64、windows/amd64。
- AS name表示为构建阶段命令,在后续FROM和COPY --from=name说明中可以使用这个名词,引用此阶段构建的映像。
- tag或digest值是可选的。如果您省略其中任何一个,构建器默认使用latest标签。如果找不到指定tag,构建起将返回错误。
ARG CODE_VERSION=latest
FROM base:$CODE_VERSION
CMD /code/run-app
FROM extras:$CODE_VERSION
CMD /code/run-extras
LABEL
LABEL 指令将元数据添加到镜像。LABEL 是键值对。要在 LABEL 值中包含空格,请像在命令行中一样使用引号和反斜杠。
WORKDIR
用来指定当前工作目录(或者称为当前目录)
当使用相对目录的情况下,采用上一个WORKDIR指定的目录作为基准
相当与cd 命令,但不同的是指定了WORKDIR后,容器启动时执行的命令会在该目录下执行
RUN
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
RUN 主要用于在Image里执行指令,比如安装软件,下载文件等。
COPY指令从复制文件、目录到镜像文件系统的。
COPY
编写Dockerfile的时候copy宿主机文件到镜像中。
CMD
Dockerfile只允许使用一次CMD命令。使用多个CMD会抵消之前所有的命令,只有最后一个命令生效。一般来说,这是整个Dockerfile脚本的最后一个命令。
FROM ubuntu
CMD ["/usr/bin/wc","--help"]
CMD有三种形式:
- CMD [“exec”,“param1”,“param2”]:使用exec执行,推荐方式。
- CMD command param1 param2:在/bin/sh中执行,可以提供交互操作。
- CMD [“param1”,“param2”]:提供给ENTRYPOINT的默认参数(极少这样使用)。
EXPOSE
EXPOSE指令通知容器在运行时监听某个端口,可以指定TCP或UDP,如果不指定协议,默认为TCP端口。但是为了安全,docker run命令如果没有带上相应的端口映射参数,Docker并不会将端口映射出去。
EXPOSE 80/tcp
EXPOSE 80/udp
指定映射端口方式:
docker run -P:将所有端口发布到主机接口,每个公开端口绑定到主机上的随机端口,端口范围在/proc/sys/net/ipv4/ip_local_port_range定义的临时端口范围内。
docker run -p :显式映射单个端口或端口范围。
ENTRYPOINT
ENTRYPOINT 指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有其他传入值作为该命令的参数。
一个Dockerfile中只能有一个ENTRYPOINT命令。如果有多条,只有最后一条有效。
无参的方式:
ENTRYPOINT [“/usr/sbin/nginx"]
指定参数的方式:
ENTRYPOINT [“/usr/sbin/nginx”, “-g”, “deamon off"]
docker run 的–entrypoint 标志可以覆盖原Dockerfile中的ENTRYPOINT 指令。
注意理解该命令, 该命令 是指定你每次 docker run启动容器的时候,都会自己执行的一个程序!!!。
CMD与ENTRYPOINT的关系
-
CMD可以为ENTRYPOINT提供参数,ENTRYPOINT本身也可以包含参数,但是可以把需要变动的参数写到CMD里面,而不需要变动的参数写到ENTRYPOINT里面;
-
ENTRYPOINT使用第二种shell方式会屏蔽掉CMD里面的命令参数和docker run后面加的命令。
-
在Dockerfile中,ENTRYPOINT指定的参数比运行docker run时指定的参数更靠前。
ENTRYPOINT/CMD最后一条命令为无限运行的命令:
这句话才是使用ENTRYPOINT的精髓。
在Docker Daemon模式下,entrypoint、cmd命令的最后一个命令,一定是要当前容器需要一直运行的,才能防止容器退出。
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令。换句话说实际执行时,会变成 “”
工作中常用技巧总结
Dockerfile中使用变量
通过关键字ARG,ENV设置变量
ARG arg1=test
ENV env1=production
注意:
- 不能通过表达如$(uname -a)进行设置,只能设置为常量
- ARG设置的变量在构建完成后,就会丢失。即在Docker中无法引用该变量
- ENV设置的变量在Docker中可以通过如$env1访问
在RUN中设置变量
在RUN通过arg=someValue中设置变量,以下脚本先获取Debain的系统版本号,并设置到了os_release变量中,在后续的命令中可以通过$os_release进行引用
RUN os_release="$(cat /etc/os-release | grep VERSION_CODENAME | awk -F '=' 'print $2')" &&\\
echo "deb http://mirrors.aliyun.com/debian/ $os_release main non-free contrib\\n\\
deb http://mirrors.aliyun.com/debian-security $os_release/updates main\\n\\
deb http://mirrors.aliyun.com/debian/ $os_release-updates main non-free contrib\\n\\
deb http://mirrors.aliyun.com/debian/ $os_release-backports main non-free contrib\\n"\\
> /etc/apt/sources.list
一个RUN命令,相当于新打开一个Shell。所以上一个RUN设置的变量无法在下一个RUN中使用。
因此如果你需要在build期间使用某些变量,那么ARG是最好的选择。如果你是想在运行期间使用,那么ENV是唯一的选择。
ENV主要是定义环境变量,在docker run的时候ENV的配置会加载到容易内部,但ARG的参数在内部是没法看到的。同时也可以通过下面命令更改ENV的默认值:
docker run -e var=yyy
如果现在我想在BUILD的时候,改变我的环境变量,而不是每次RUN的时候更改,需要怎么做?
ARG和ENV 两者结合使用
ARG var
ENV var=$var
在dockerfile内部可以这样控制命令的参数。
ARG protocal
ARG address
ARG port
ENV protocal=$protocal \\
address=$address \\
port=$port
CMD /usr/bin/lightweightservicediscovery --listen=$PROTOCAL:-ipv4:$ADDRESS:-0.0.0.0:$port:-49188
//如果读取环境变量失败再采用后面的默认值。
这样既可以在build的时候通过docker build --build-arg var=xxx 来传递参数,也可以通过在运行的时候通过docker run -e var=yyy来传递参数。
实战小demo:
docker build --build-arg INSTALL_ZIP=myinstall.zip -t centos-test:v4 .
ARG INSTALL_ZIP
COPY ./$INSTALL_ZIP /root/
RUN chmod 755 $INSTALL_ZIP
RUN unzip $INSTALL_ZIP
三、如何制作Docker镜像?
docker build 命令制作docker镜像
docker build 命令用于使用 Dockerfile 创建镜像。
docker build [OPTIONS] PATH | URL | -
OPTIONS说明:
- –build-arg=[] :设置镜像创建时的变量;
- –cpu-shares :设置 cpu 使用权重;
- –cpu-period :限制 CPU CFS周期;
- –cpu-quota :限制 CPU CFS配额;
- –cpuset-cpus :指定使用的CPU id;
- –cpuset-mems :指定使用的内存 id;
- –disable-content-trust :忽略校验,默认开启;
- -f :指定要使用的Dockerfile路径;
- –force-rm :设置镜像过程中删除中间容器;
- –isolation :使用容器隔离技术;
- –label=[] :设置镜像使用的元数据;
- -m :设置内存最大值;
- –memory-swap :设置Swap的最大值为内存+swap,"-1"表示不限swap;
- –no-cache :创建镜像的过程不使用缓存;
- –pull :尝试去更新镜像的新版本;
- –quiet, -q :安静模式,成功后只输出镜像 ID;
- –rm :设置镜像成功后删除中间容器;
- –shm-size :设置/dev/shm的大小,默认值是64M;
- –ulimit :Ulimit配置。
- –squash :将 Dockerfile 中所有的操作压缩为一层。
- –tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
- –network: 默认 default。在构建期间设置RUN指令的网络模式
使用当前目录的 Dockerfile 创建镜像,标签为 runoob/ubuntu:v1。
docker build -t runoob/ubuntu:v1 .
也可以通过 -f Dockerfile 文件的位置:
$ docker build -f /path/to/a/Dockerfile .
go build工作中遇到的常见问题
“docker build” requires exactly 1 argument.
问题描述:
在参照这docker官网教程学习构建镜像的时候。提示错误:“docker build” requires exactly 1 argument.
问题分析:
原因是因为(少了一个 ‘.’ , ‘.’ 代表当前路径):
docker build --tag=xxx .
报错:file not found in build context or excluded by .dockerignore
问题描述:
dockerfile中 执行copy 把主机上的文件往容器中copy时,报错file not found in build context or excluded by .dockerignore
问题分析:
我的dockerfile中 宿主机的文件,写的绝对路径。而 dockerfile 不能获取 父目录。
解决方案:
文件放置在当前路径下,dockerfile中 写成 ./文件名 即可(将文件copy到当前目录)。
构建 Go 应用 docker 镜像
构建 Go 应用 docker 镜像
https://zhuanlan.zhihu.com/p/476921483
之前我们的演示的是 centos:centos8
基础镜像,查看原文,作者使用了 golang:alpine
镜像,非常小。
原作者的 dockerfile demo:
FROM golang:alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
ENV GOPROXY https://goproxy.cn,direct
RUN apk update --no-cache && apk add --no-cache tzdata
WORKDIR /build
ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/hello ./hello.go
FROM alpine
RUN apk update --no-cache && apk add --no-cache ca-certificates
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
ENV TZ Asia/Shanghai
WORKDIR /app
COPY --from=builder /app/hello /app/hello
CMD ["./hello"]
- 默认禁用了 cgo
- 启用了 GOPROXY 加速 go mod download
- 去掉了调试信息 -ldflags=“-s -w” 以减小镜像尺寸
- 安装了 ca-certificates,这样使用 TLS证书就没问题了
- tzdata 在 builder 镜像安装,并在最终镜像只拷贝了需要的时区
- 自动设置了本地时区,这样我们在日志里看到的是北京时间了
Dockerfile 构建出的镜像大小和 v1 centos对比结果如下:
$ docker images | grep hello
hello v4 94ba3ece3071 4 hours ago 6.66MB
hello v3 f51e1116be11 8 hours ago 6.61MB
hello v2 0dd53f016c93 8 hours ago 6.61MB
hello v1 ac0e37173b85 9 hours ago 314MB
docker镜像存储位置
docker info | grep "Docker Root Dir"
root@cka-k8s-master:~/code/mygoapp# cd /data/docker
root@cka-k8s-master:/data/docker# ls
buildkit containers image network overlay2 plugins runtimes swarm tmp trust volumes
root@cka-k8s-master:/data/docker# du -sh * | sort -h
4.0K runtimes
4.0K swarm
4.0K tmp
4.0K trust
16K plugins
28K volumes
80K network
88K buildkit
4.6M image
8.1M containers
3.1G overlay2
root@cka-k8s-master:/data/docker#
个人demo:go 项目docker镜像Dockerfile
原始镜像的选择?
centos:centos8作为基础镜像,是因为centos:centos8镜像中包含了基本的排障工具,例如vi、cat、curl、mkdir、cp等工具。
FROM centos:centos8
LABEL maintainer="<xxx@yeah.net>"
WORKDIR /opt/mygoapp
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \\
echo "Asia/Shanghai" > /etc/timezone && \\
mkdir -p /var/log/mygoapp
COPY file-boom /opt/mygoapp/bin/
ENTRYPOINT ["/opt/mygoapp/bin/file-boom"]
CMD ["-c", "/etc/mygoapp/file-boom.yaml"]
以上是关于关于制作Docker镜像?| Dockerfile快速开始的主要内容,如果未能解决你的问题,请参考以下文章
Docker学习笔记 —— 镜像制作(Dockerfile)
Docker学习笔记 —— 镜像制作(Dockerfile)
Docker Review - dockerfile 实战_使用dockerfile制作tomcat镜像