携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
在使用 Docker 的过程中,经常会用到别人已经制作好的镜像。有些时候不符合我们的需求,在这种场景下我们也会构建定制化的镜像,例如在现有镜像中添加功能。
目前,Docker 官方提供了两种构建方案:
1、基于容器创建
2、基于 Dockerfile 创建
这两种方案各有千秋,本文将了解这两种方案。
一、基于容器创建镜像
这种方式最直观明了,在操作上也非常简单,整个过程只用三步:
- 创建容器
- 进入容器中修改内容
- 将容器保存为镜像
下面用 nginx 镜像为例演示:
1、创建目标容器
$ docker run -d --name nginx nginx
09eccb8f8666bdb7657b55d11720ef1c90602e66aa5138c47a59adb5c2300cdb
2、进入容器修改内容
$ docker exec -it nginx bash
root@09eccb8f8666:/#
我们在容器中新建一个文件
root@09eccb8f8666:/# touch test.txt
完成之后退出容器
3、将容器保存为镜像
$ docker commit nginx nginx-new:1.0
sha256:e99efd691e913c53ba21d5dbcd9156cc5591a576cb1c8eb61a75c5e0176767ba
查看镜像列表,可看到新的镜像已经生成
$ docker images | grep nginx
nginx-new 1.0 e99efd691e91 2 minutes ago 141MB
nginx latest 605c77e624dd 6 months ago 141MB
基于容器创建的方式虽然简单直观,但是不被推荐。这种方式需要创建容器然后手工操作,不易被复制,使用者无法了解镜像的整个创建问题,可能会引发其他问题。
二、基于 Dockerfile 创建镜像
Dockerfile 作为文本文件,在文件中包含了镜像的创建指令,用户可以通过它快速创建定制化镜像。
以上述 nginx 镜像制作过程为例
创建一个空目录,在目录中创建 Dockerfile 的文件,并写入如下内容
FROM nginx
RUN touch test.txt
完成后,执行命令
$ docker build -t nginx-new:2.0 .
docker build 命令用于生成镜像,参数
-t为镜像打 tag,tag 用于标记镜像版本信息,默认 tag 为 latest,最后的.表示当前目录为执行目录。
命令执行完成后,查看镜像列表可看到已成功创建。
Dockerfile 可以看做是 docker commit 的代码化过程,能够使得镜像创建流程自动化,配合 CI/CD 流水线使用,这会极大提高效率。
Dockerfile语法可按照功能简单分为两类:配置指令和操作指令。配置指令
FROM 指令
格式:FROM
描述:指定基于
image基础镜像来制作新的镜像,在Dockerfile中必须存在。通常为了避免镜像臃肿,占用空间大,在满足需求的前提下,尽量使用精简镜像,如 alpine 镜像,各官方镜像通常都有对应的 alpine 镜像,如golang:alipine。示例:
FROM golang:alpineMAINTAINER 指令
格式:MAINTAINER <author_name>
描述:指定镜像的作者信息
示例:
MAINTAINER AlexUSER指令
格式:USER [:]
描述:用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)
示例:
USER rootEXPOSE指令
格式:EXPOSE []
描述:用于声明容器内服务的监控端口,这里只是起到声明作用,并不会映射到宿主机端口。如果要映射宿主机端口,需要在容器启动时使用参数
-P会随机映射端口。示例:
EXPOSE 8088ENV指令
格式:ENV
描述:指定容器的环境变量,该变量可以被
RUN指令使用,并在容器运行后一直存在示例:
ENV KEY valueARG指令
格式:ARG [=]
描述:构建参数,与ENV作用一致,但作用域不同。ARG设置的环境变量仅在
Dockerfile内有效,也就是说在docker build的过程中有效,构建过程中可以使用`docker build --build-arg <参数名>=<值>来覆盖ARG ARCH=amd64VOLUME指令
格式:VOLUME
描述:创建一个数据卷挂载点,一般用来存放需要持久化的数据
示例:
VOLUME ["/data"]WORKDIR指令
格式:WORKDIR
描述:指定容器的工作目录
示例:
WORKDIR /home操作指令
RUN指令
格式:RUN
描述:在容器中运行的命令,当执行
RUN指令时,会生成一个镜像层来保存结果内容,为了减少镜像体积,通常用&&将多个命令放到单个RUN指令中执行。示例:
RUN tdnf install sudo tzdata -y >> /dev/null \ && tdnf clean allCOPY指令
格式:COPY
描述:复制宿主机文件或目录到镜像中
示例:
COPY test.txt /homeADD指令
格式:ADD
描述:与COPY类似,但如果
src是tar文件会被解压到dest,同时src支持URL资源。示例:
ADD test.tar /HEALTHCHECK指令
格式:HEALTHCHECK CMD
描述:用于指定某个程序或者指令来监控 docker 容器服务的运行状态。
示例:
HEALTHCHECK CMD curl --fail -s http://127.0.0.1:8080/api/ping || exit 1CMD指令
格式:CMD ["executable","param1","param2"]
描述:容器启动后默认运行的命令,每个
Dockerfile只能有一条CMD命令,如果有多条则执行最后一条。CMD命令可以在docker run启动容器后被替换。CMD ["nginx","-g","daemon off;"]ENTRYPOINT指令
格式:ENTRYPOINT ["executable","param1","param2"]
描述:与CMD指令类似,但ENTRYPOINT在容器启动时无法被替换
示例:
ENTRYPOINT ["nginx","-g","daemon off;"]
三、实践
在理解Dockerfile的相关指令后,我们来将一个应用容器化。
我们以一个Golang程序为例,在main.go文件中写入如下内容:
package main
import "net/http"
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
})
http.ListenAndServe(":8080", nil)
}
编写Dockerfile
FROM golang:1.17.9 as builder
COPY main.go /go/src/
WORKDIR /go/src
ENV GO111MODULE off
RUN CGO_ENABLED=0 go build -o web
FROM alpine:latest
COPY --from=builder /go/src/web web
EXPOSE 8080
CMD ["./web"]
执行命令开始构建
$ docker build -t web-test:v1 .
构建完成,查看构建的镜像
$ docker images
web-test v1 b4986dbf8f6b About a minute ago 11.7MB
可以通过history命令查看镜像构建过程中执行了哪些操作
$ docker history web-test:v1
IMAGE CREATED CREATED BY SIZE COMMENT
b4986dbf8f6b 2 minutes ago /bin/sh -c #(nop) CMD ["web"] 0B
2f17c96210ac 2 minutes ago /bin/sh -c #(nop) EXPOSE 8080 0B
9647d0925951 2 minutes ago /bin/sh -c #(nop) COPY file:37a80aa6fbfae307… 6.07MB
c059bfaa849c 8 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 8 months ago /bin/sh -c #(nop) ADD file:9233f6f2237d79659… 5.59MB
运行容器
$ docker run -p 8080:8080 -d --name web-test web-test:v1
浏览器访问地址,可看到服务已正常运行