dockerfile——镜像构建工具详解及案例

发布时间:2023年12月27日

Dockerfile

Dockerfile是?个创建镜像所有命令的?本?件, 包含了?条条指令和说明, 每条指令构建?层, 通过docker build命令,根据Dockerfile的内容构建镜像,因此每?条指令的内容, 就是描述该层如何构建.有了Dockefile, 就可以制定??docker镜像规则,只需要在Dockerfile上添加或者修改指令, 就可?成docker 镜像.

docker build 流程简介

docker build命令会读取Dockerfile的内容,并将Dockerfile的内容发送给 Docker 引擎,最终Docker 引擎会解析Dockerfile中的每?条指令,构建出需要的镜像。

  • 第?步,docker build会将 context 中的?件打包传给 Docker daemon。如果 context 中有.dockerignore?件,则会从上传列表中删除满?.dockerignore规则的?件。注意:如果上下?中有相当多的?件,可以明显感受到整个?件发送过程
  • 第二步,docker build命令向 Docker server 发送 HTTP 请求,请求 Docker server 构建镜像,请求中包含了需要的 context 信息。
  • 第三步,Docker server 接收到构建请求之后,会执?以下流程来构建镜像:
    • 创建?个临时?录,并将 context 中的?件解压到该?录下。
    • 读取并解析 Dockerfile,遍历其中的指令,根据命令类型分发到不同的模块去执?。
    • Docker 构建引擎为每?条指令创建?个临时容器,在临时容器中执?指令,然后 commit 容器,?成?个新的镜像层。
    • 最后,将所有指令构建出的镜像层合并,形成 build 的最后结果。最后?次 commit ?成的镜像 ID就是最终的镜像 ID。

为了提?构建效率,docker build默认会缓存已有的镜像层。如果构建镜像时发现某个镜像层已经被缓存,就会直接使?该缓存镜像,?不?重新构建。如果不希望使?缓存的镜像,可以在执?docker build命令时,指定--no-cache=true参数。

Docker 匹配缓存镜像的规则
遍历缓存中的基础镜像及其?镜像,检查这些镜像的构建指令是否和当前指令完全?致,如果不?样,则说明缓存不匹配。对于ADD、COPY指令,还会根据?件的校验和(checksum)来判断添加到镜像中的?件是否相同,如果不相同,则说明缓存不匹配。缓存匹配检查不会检查容器中的?件。?如,当使?RUN apt-get -y update命令更新了容器中的?件时,缓存策略并不会检查这些?件,来判断缓存是否匹配。最后,可以通过docker history命令来查看镜像的构建历史

Dockerfile关键字

FROM 设置镜像使?的基础镜像
MAINTAINER 设置镜像的作者
RUN 编译竟像时运?的脚步
CMD 设置容器的启动命令
LABEL 设置镜像标签
EXPOSE 设置镜像暴露的端?
ENV 设置容器的环境变量
ADD 编译镜像时复制上下?中?件到镜像中
COPY 编译镜像时复制上下?中?件到镜像中
ENTRYPOINT 设置容器的??程序
VOLUME 设置容器的挂载卷
USER 设置运? RUN CMD ENTRYPOINT的?户名
WORKDIR 设置 RUN CMD ENTRYPOINT COPY ADD 指令的?作?录
ARG 设置编译镜像时加?的参数
ONBUILD 设置镜像的ONBUILD 指令
STOPSIGNAL 设置容器的退出信号量

案例

素材

一个简单的http服务器,打印启动参数和一些环境变量

hello目录下

printEnv.go

package hello

import (
	"fmt"
	"net/http"
	"os"
)

type EnvParam struct {
}

func (*EnvParam) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	env1 := os.Getenv("env1")
	env2 := os.Getenv("env2")
	fmt.Printf("env list : env1 = %s and env2 = %s", env1, env2)
	fmt.Println()
	fmt.Fprintf(w, "env list : env1 = %s and env2 = %s", env1, env2)
}

printStartParam.go

package hello

import (
	"flag"
	"fmt"
	"net/http"
)

const (
	defaultStartUpParam = "default"
)

var (
	param1 = flag.String("param1", defaultStartUpParam, "param1 to hello world")
	param2 = flag.String("param2", defaultStartUpParam, "param2 to hello world")
)

func init() {
	flag.Parse()
}

type PrintStartParam struct {
}

func (*PrintStartParam) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Printf("start up params:     param1 = %s and param2 = %s ", *param1, *param2)
	fmt.Println()
	fmt.Fprintf(w, "start up params:     param1 = %s and param2 = %s ", *param1, *param2)
}

main.go

package main

import (
	"fmt"
	"httpServer/hello"
	"net/http"
)

func main() {
	fmt.Println("into main")
	http.Handle("/print/env", new(hello.EnvParam))
	http.Handle("/print/startup", new(hello.PrintStartParam))
	http.ListenAndServe(":80", nil)
	//	http.ListenAndServeTLS()
}

docker build构建镜像
FROM golang:1.18
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
#也可以用git拉取代码 RUN git clone https://gitee.com/dongyademo/helloworld.git
#这里从上下文中拷贝文件
COPY ./httpServer /go/src/httpServer
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

docker build -t httphello:1.0.0 -f Dockerfile .
运行测试
docker run -p 8081:80 -d --name testServer httphello:1.0.0

[root@localhost example3]# curl http://localhost:8081/print/env
env list : env1 = env1value and env2 = env2value

查看镜像

[root@localhost example3]# docker images
REPOSITORY                        TAG       IMAGE ID       CREATED         SIZE
httphello                         1.0.0     8ae621256ce2   23 hours ago    988MB
alpine                            latest    f8c20f8bbcb6   2 weeks ago     7.38MB

这破镜像居然有988MB。最终运行的时候就运行一个二进制文件,所以编译时候的环境我们最终运行的时候是基本不需要的。如何缩小打出来的镜像呢呢,可以考虑用多阶段构建

多阶段构建

Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许?个Dockerfile 中出现多个 FROM 指令。这样做有什么意义呢?

多个 FROM 指令的意义

多个 FROM 指令并不是为了?成多根的层关系,最后?成的镜像,仍以最后?条 FROM 为准,之前的 FROM 会被抛弃,那么之前的FROM ?有什么意义呢?

每?条 FROM 指令都是?个构建阶段,多条 FROM 就是多阶段构建,虽然最后?成的镜像只能是最后?个阶段的结果,但是,能够将前置阶段中的?件拷?到后边的阶段中,这就是多阶段构建的最?意义。

最?的使?场景是将编译环境和运?环境分离,?如,之前我们需要构建?个Go语?程序,那么就需要?到go命令等编译环境

Dockerfile

FROM golang:1.18
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .

FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0 # 这个label 不是打包出来的镜像tag,只是config里的字段
WORKDIR /app/
#--from=0代表从阶段0进行操作
COPY --from=0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

除了用--from=0指定阶段,还可以通过as关键词,为构建阶段指定别名,也提?了可读性

FROM golang:1.18 as stage0
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .

FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
WORKDIR /app/
#--from=stage0代表从阶段stage0进行操作
COPY --from=stage0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

构建镜像

docker build -t httphello:1.0.0 -f Dockerfile .

查看镜像,这个时候我们发现打出来的镜像就小很多了,因为是基于一个轻量级linux alpine打包出来的。多阶段构建基于最后一个from来构建

[root@localhost example3]# docker images
REPOSITORY                        TAG       IMAGE ID       CREATED         SIZE
helloserver                       1.0.2     3eaa5b73a5b4   4 hours ago     13.9MB
httphello                         1.0.0     8ae621256ce2   23 hours ago    988MB
alpine                            latest    f8c20f8bbcb6   2 weeks ago     7.38MB

运行测试
docker run -p 8081:80 -d --name testServer helloserver:1.0.2

[root@localhost example3]# curl http://localhost:8081/print/env
env list : env1 = env1value and env2 = env2value

ADD和COPY

  • ADD 与 COPY 不能拷?上下?以外的?件
  • COPY 命令语法格式

COPY语法

COPY <src> <dest> //将上下?中源?件,拷?到?标?件
COPY prefix* /destDir/ //将所有prefix 开头的?件拷?到 destDir ?录下
COPY prefix?.log /destDir/ //?持单个占位符,例如 : prefix1.log、
prefix2.log 等
  • 对于?录??,COPY 和 ADD 命令具有相同的特点:只复制?录中的内容?不包含?录?身
COPY srcDir /destDir/ //只会将源?件夹srcDir下的?件拷?到 destDir ?录下
  • COPY 区别于ADD在于Dockerfile中使?multi-stage。可以拷贝不同阶段的文件
FROM golang:1.18 as stage0
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .

FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
WORKDIR /app/
#--from=stage0代表从阶段stage0进行操作
COPY --from=stage0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

ADD语法

ADD <src> <dest>

ADD 命令除了不能?在 multistage 的场景下,ADD 命令可以完成 COPY 命令的所有功能,并且还可以完成两类的功能:

  • 解压压缩?件并把它们添加到镜像中,对于宿主机本地压缩?件,ADD命令会?动解压并添加到镜像
  • 从 url 拷??件到镜像中,需要注意:url 所在?件如果是压缩包,ADD 命令不会?动解压缩

例如把nginx打包进镜像里

FROM golang:1.18 as stage0
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .

FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
ADD https://nginx.org/download/nginx-1.21.6.tar.gz /soft/
COPY nginx-1.21.6.tar.gz /soft/copy/
ADD nginx-1.21.6.tar.gz /soft/add/
WORKDIR /app/
#--from=stage0代表从阶段stage0进行操作
COPY --from=stage0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

启动进容器里查看对应目录即可

docker build --no-cache -t helloserver:1.0.3 -f Dockerfile .
docker run -p 8081:80 -d --name helloserver helloserver:1.0.3
docker exec -it d873894c0493 /bin/sh

注意:ADD?标?件位置要注意路径后?是否带 “/” ,带斜杠表示?录,不带斜杠表示?件名?件名?带有空格,需要再 ADD(或COPY)指令??双引号的形式标明:

ADD "space file.txt" "/tmp/space file.txt"

CMD 和 ENTRYPOINT

CMD
# shell 格式
CMD <command>
# exec格式,推荐格式
CMD ["executable","param1","param2"]
# 为ENTRYPOINT 指令提供参数
CMD ["param1","param2"]

CMD 指令提供容器运?时的默认值,这些默认值可以是?条指令,也可以是?些参数。?个dockerfile中可以有多条CMD指令,但只有最后?条CMD指令有效。CMD参数格式是在CMD指令与ENTRYPOINT指令配合时使?,CMD指令中的参数会添加到ENTRYPOINT指令中。使?shell 和exec 格式时,命令在容器中的运??式与RUN 指令相同。不同在于,RUN指令在构建镜像时执?命令,并?成新的镜像。CMD指令在构建镜像时并不执?任何命令,?是在容器启动时默认将CMD指令作为第?条执?的命令。如果在命令?界?运?docker run 命令时指定命令参数,则会覆盖CMD指令中的命令。

例子,前文也提到

FROM golang:1.18 as stage0
ADD ./httpServer /go/src/httpServer/
WORKDIR /go/src/httpServer
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .

FROM alpine:latest
ENV env1=env1value
ENV env2=env2value
MAINTAINER dongya
LABEL hello 1.0.0
WORKDIR /app/
#--from=stage0代表从阶段stage0进行操作
COPY --from=stage0 /go/src/httpServer/app ./
EXPOSE 80
CMD ["./app","--param1=p1","--param2=p2"]

ENTRYPOINT

ENTRYPOINT指令有两种格式

# shell 格式
ENTRYPOINT <command>
# exec 格式,推荐格式
ENTRYPOINT ["executable","param1","param2"]

ENTRYPOINT指令和CMD指令类似,都可以让容器每次启动时执?相同的命令,但它们之间?有不同。?个Dockerfile中可以有多条ENTRYPOINT指令,但只有最后?条ENTRYPOINT指令有效。当使?shell格式时,ENTRYPOINT指令会忽略任何CMD指令和docker run 命令的参数,并且会运?在bin/sh -c。推荐使?exec格式,使?此格式,docker run 传?的命令参数将会覆盖CMD指令的内容并且附加到ENTRYPOINT指令的参数中。

CMD可以是参数,也可以是指令,ENTRYPOINT只能是命令;docker run 命令提供的运?命令参数可以覆盖CMD,但不能覆盖ENTRYPOINT。

# syntax=docker/dockerfile:1
FROM golang:1.18
ENV env1=env1value
ENV env2=env2value
MAINTAINER nick
LABEL hello 1.0.0
RUN git clone https://gitee.com/nickdemo/helloworld.git
WORKDIR helloworld
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app .
EXPOSE 80
#ENTRYPOINT ["./app"]
#ENTRYPOINT ./app --param1=p11.0.3 --param2=p2
ENTRYPOINT ["./app","--param1=p1","--param2=p2"]
#CMD ["./app","--param1=p1","--param2=p2"]
#CMD ./app --param1=p1 --param2=p2
CMD ["--param1=p1","--param2=p2"]
替换CMD和ENTRYPOINT参数
#shell形式
docker run <image-name> --cmd "param1 param2 param3"
docker run --entrypoint "command" <image-name> "param1" "param2" "param3"
#exec命令形式
docker run --entrypoint '["command", "param1", "param2", "param3"]' <image-name>
docker run <image-name> --cmd '["command", "param1", "param2", "param3"]'

CMD和ENTRYPOINT一起使用

FROM alpine:latest
ENTRYPOINT ["echo"]
CMD ["Hello, World!"]

参数替换

docker run --entrypoint "echo" <image-name> "Hello, Docker!"

--entrypoint 参数必须在最前面,紧随其后的是镜像名称,然后是要传递的命令及参数

文章来源:https://blog.csdn.net/qq_43058348/article/details/135250123
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。