您当前的位置: 首页 >  docker

暂无认证

  • 0浏览

    0关注

    92582博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Dockerfile实践之多阶段构建

发布时间:2020-10-30 05:23:16 ,浏览量:0

在这里插入图片描述

在Docker 17.05之后,Docker在构建中支持了多阶段构建,简单来说,Dockerfile中可以有多个FROM,这篇文章通过一个简单的示例来说明多阶段构建的使用场景和方式。

目录
  • 基本语法
  • 场景说明
    • 代码示例
    • 构建go语言应用
    • 构建go应用镜像
    • 运行go应用容器
  • 上述场景的其他解法
    • 解法示例Dockerfile
    • 分阶段方式的构建
    • 运行go应用容器
  • 总结
基本语法

FROM的三种语法格式如下所示:

语法格式1:FROM [--platform=][AS]

语法格式2:FROM [--platform=][:] [AS]

语法格式3:FROM [--platform=][@] [AS]

场景说明

比如有如下go语言的类似spring boot的web应用,提供在8080端口提供信息显示服务。此应用构建时需要go语言的编译环境,而一旦运行的时候则只需要一个Alpine容器即可,一般来说可以分两步进行,先编译,编译完生成的二进制文件再进行构建生成可执行的容器。

代码示例
liumiaocn:go liumiao$ cat basic-web-hello.go package main import "fmt" import "net/http" func Hello(response http.ResponseWriter, request *http.Request) { fmt.Fprintf(response, "Hello, LiuMiao, Welcome to go web programming...\n") } func main() { http.HandleFunc("/", Hello) http.ListenAndServe(":8080", nil) } liumiaocn:go liumiao$
构建go语言应用

执行命令:docker pull golang:1.15.3-alpine3.12

可以看到此编译环境即使是alpine方式也是不小的,如果运行时使用此镜像作为Base镜像大小比较浪费,如果用在边缘设备上基本上不太可行。

liumiaocn:go liumiao$ docker images |grep 1.15.3-alpine3.12
golang                                                               1.15.3-alpine3.12               d099254f5fc3        7 days ago          299MB
liumiaocn:go liumiao$

使用此镜像进行构建

liumiaocn:go liumiao$ docker run --rm -it -v `pwd`:/build golang:1.15.3-alpine3.12 sh
/go # cd /build
/build # ls
basic-web-hello.go
/build # go build -o http-server basic-web-hello.go 
/build # ls
basic-web-hello.go  http-server
/build #

可以看到已经成功构建出来所需要的二进制文件http-server了

构建go应用镜像

此处使用一个Alpine镜像即可运行此go语言应用了,我们这里使用如下简单的Dockerfile来实现

liumiaocn:go liumiao$ cat Dockerfile 
FROM alpine:3.12.1 

WORKDIR /target
COPY    http-server /target/

EXPOSE  8080
ENTRYPOINT ["/target/http-server"]
liumiaocn:go liumiao$

然后即可进行镜像的构建了

liumiaocn:go liumiao$ docker build -t test-go:latest .
Sending build context to Docker daemon  6.453MB
Step 1/5 : FROM alpine:3.12.1
 ---> d6e46aa2470d
Step 2/5 : WORKDIR /target
 ---> Running in da7d4cae785c
Removing intermediate container da7d4cae785c
 ---> ae974a6c826c
Step 3/5 : COPY    http-server /target/
 ---> 3fce2c8c9c1c
Step 4/5 : EXPOSE  8080
 ---> Running in dc98c1a16510
Removing intermediate container dc98c1a16510
 ---> 395a0c569fb1
Step 5/5 : ENTRYPOINT ["/target/http-server"]
 ---> Running in e30b500061ea
Removing intermediate container e30b500061ea
 ---> da9281ce2a5f
Successfully built da9281ce2a5f
Successfully tagged test-go:latest
liumiaocn:go liumiao$

可以看到构建的镜像大小也比较适中

liumiaocn:go liumiao$ docker images |grep test-go
test-go                                                              latest                          da9281ce2a5f        2 minutes ago       12MB
liumiaocn:go liumiao$
运行go应用容器

这里使用docker run命令运行此容器,也可以使用其他各种方式,比如docker-compose或者在kubernetes中

liumiaocn:go liumiao$ docker run --rm -p 8080:8080 test-go

在另外的终端或者浏览器中进行确认此8080端口启动的go的web应用

liumiaocn:go liumiao$ curl http://localhost:8080
Hello, LiuMiao, Welcome to go web programming...
liumiaocn:go liumiao$

可以看到已经可以正常运行了

上述场景的其他解法

上述场景是一个非常常见的开发过程的编译、构建和运行阶段的操作,比较典型的就是构建阶段所用到的很多依赖和环境在最终的结果中可能只是部分需要,所以导致的问题就是要分开来做,构建产生二进制文件,然后将此二进制文件拿出来然后再进行构建,而在Docker 17.03之后的这个版本,现在有了新的解法了。

解法示例Dockerfile

首先来看一个示例解法,使用如下Dockerfile可以直接解决此问题

liumiaocn:go liumiao$ cat Dockerfile 
ARG  GOLANG_VER=1.15.3
ARG  ALPINE_VER=3.12
FROM golang:${GOLANG_VER}-alpine${ALPINE_VER} as go-builder-1.15.3

WORKDIR /build

COPY basic-web-hello.go /build
RUN  cd /build && go build -o http-server basic-web-hello.go

FROM alpine:${ALPINE_VER}

WORKDIR /target

COPY --from=go-builder-1.15.3 /build/http-server /target/

EXPOSE  8080
ENTRYPOINT ["/target/http-server"]
liumiaocn:go liumiao$

可以看到这个Dockerfile和普通的Dockerfile略有区别,就是感觉是两个Dockerfile拼接在一起的,后续的Dockerfile中对于前面的关联仅仅在于所生成的二进制文件,它的好处?IAAC算不算一个回答?算。原本需要手工操作才能完成的连接工作,将此二进制文件和其相应的Dockerfile进行关联,但是考虑到比如此二进制文件的版本、支持的操作系统的体系架构、权限设定、传输产生的不完整性风险等,以及相关的Dockerfile的版本变化以及人为的误操作因素等,这么小的一个点也可能产生很多问题。而在一个Dockerfile中就没有这个问题了,因为输入的go语言应用,输出的是最终的应用镜像,没有中间的其他结果,那我们来看一下这种方式下的构建过程。

分阶段方式的构建
liumiaocn:go liumiao$ docker build -t go-app .
Sending build context to Docker daemon  3.072kB
Step 1/11 : ARG  GOLANG_VER=1.15.3
Step 2/11 : ARG  ALPINE_VER=3.12
Step 3/11 : FROM golang:${GOLANG_VER}-alpine${ALPINE_VER} as go-builder-1.15.3
 ---> d099254f5fc3
Step 4/11 : WORKDIR /build
 ---> Running in 64c975a9affe
Removing intermediate container 64c975a9affe
 ---> 11f7f0fed685
Step 5/11 : COPY basic-web-hello.go /build
 ---> e0fae581e05b
Step 6/11 : RUN  cd /build && go build -o http-server basic-web-hello.go
 ---> Running in 025158d79879
Removing intermediate container 025158d79879
 ---> 1cb815aa110f
Step 7/11 : FROM alpine:${ALPINE_VER}
 ---> d6e46aa2470d
Step 8/11 : WORKDIR /target
 ---> Using cache
 ---> ae974a6c826c
Step 9/11 : COPY --from=go-builder-1.15.3 /build/http-server /target/
 ---> 7671328fc17e
Step 10/11 : EXPOSE  8080
 ---> Running in 9db998af6571
Removing intermediate container 9db998af6571
 ---> cf5362c2063a
Step 11/11 : ENTRYPOINT ["/target/http-server"]
 ---> Running in d51adf74a6e2
Removing intermediate container d51adf74a6e2
 ---> 0dfeb1ca46df
Successfully built 0dfeb1ca46df
Successfully tagged go-app:latest
liumiaocn:go liumiao$

可以看到构建的过程包括了编译和最终结果的生成,而我们所关心的是这个镜像是否像我们期待的那样没有包括go的编译环境里面几百兆的内容呢?执行docker images可以看到

liumiaocn:go liumiao$ docker images |grep go-app
go-app                                                               latest                          0dfeb1ca46df        7 seconds ago       12MB
liumiaocn:go liumiao$

正是我们所期待的结果,接下来我们运行一下此go应用,看看是否能够正常动作。

运行go应用容器

同样这里也使用docker run命令运行此容器

liumiaocn:go liumiao$ docker run --rm  -d -p 8080:8080 go-app
3f3b951caab2c91f00157966571aae6c90c08029b65e80dbb8d91f1b49888228
liumiaocn:go liumiao$ docker ps |grep go-app
3f3b951caab2        go-app                          "/target/http-server"   6 seconds ago       Up 4 seconds          0.0.0.0:8080->8080/tcp                                               trusting_shamir
liumiaocn:go liumiao$

在另外的终端或者浏览器中进行确认此8080端口启动的go的web应用

liumiaocn:go liumiao$ curl http://localhost:8080
Hello, LiuMiao, Welcome to go web programming...
liumiaocn:go liumiao$

可以看到已经可以正常运行了

总结

实际上分阶段构建主要应用了Docker 17.03之后的FROM as语法,如果as省略的话from参数可以直接使用0这样的需要进行引用,结合起来还是能够实现很多有用的功能的。从本质上来说Dockerfile是单根模式的,但是在实际使用中,有时会出现可能会需要两个不同的Base镜像的内容的可能,但是在本质上并没有改变其单根的特点,比如本文中最终所生成的go语言应用,它的根是Alpine镜像,它继承了Alpine镜像的所有内容,但是同时又需要另外一个镜像中的某个文件,这实际上是一个非常常见的场景,也可以看出Docker也是一直根据用户需求在不断地进行功能演化。

关注
打赏
1653961664
查看更多评论
立即登录/注册

微信扫码登录

0.3770s