• Docker下如何实现镜像多阶级构建?


    Docker下如何实现镜像多阶级构建?

    在Docker早期版本中,对于编译型语言(例如C、Java、Go)的镜像构建,我们只能将应用的编译和运行环境的准备,全部放在一个Dockerfile里面,这就导致我们构建出来的镜像体积很大,从而增加了镜像的存储和分发成本。

    1、借助额外脚本构建

    为了减小镜像体积,我们需要借助一个额外的脚本,将镜像的编译过程和运行过程分开。

    • 编译阶段:负责将我们的代码编译成可执行的二进制文件。
    • 运行时构建节点:准备应用程序运行的依赖环境,然后将编译好的可执行对象拷贝到镜像中。

    以Go开发的一个HTTP服务为例,代码如下:

    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    )
    
    func getInfoHandler(w http.ResponseWriter, r *http.Request){
    	user := r.URL.Query().Get("user")
    	if user != "" {
    		io.WriteString(w,fmt.Sprintf("hello [%s]\n",user))
    	}else{
    		io.WriteString(w,"hello [Stranger]\n")
    	}
    
    	for k, v := range r.Header{
    		io.WriteString(w,fmt.Sprintf("%s=%s",k,v))
    	}
    }
    
    func health(w http.ResponseWriter, r *http.Request){
    	fmt.Fprintln(w,http.StatusOK)
    }
    
    func main() {
    	http.HandleFunc("/",getInfoHandler)
    	http.HandleFunc("/health",health)
    	http.ListenAndServe(":8080",nil)
    }
    

    将这个 Go 服务构建成镜像分为两个阶段:代码的编译阶段和镜像构建阶段。

    我们构建镜像时,镜像中需要包含 Go 语言编译环境,应用的编译阶段我们可以使用 Dockerfile.build 文件来构建镜像。Dockerfile.build 的内容如下:

    FROM golang:1.15
    WORKDIR /go/src/httpServer/
    COPY main.go .
    RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
    

    Dockefile.Build可以帮助我们把代码编译成可执行的二进制文件,我们使用以下的Dockerfile构建一个运行环境:

    FROM alpine:latest  
    WORKDIR /root/
    COPY http-server .
    CMD ["./http-server"] 
    

    我们将应用的编译和运行环境的准备步骤,都放到一个 build.sh 脚本文件中,内容如下:

    #!/bin/sh   
    echo Building http-server:build    # 声明shell文件,然后输出开始构建信息
    
    # 使用 Dockerfile.build 文件来构建一个临时镜像 http-server:build
    docker build -t http-server:build . -f Dockerfile.build
    
    # 用 http-server:build 镜像创建一个名称为 builder 的容器
    # 该容器包含编译后的 http-server 二进制文件。
    docker create --name builder http-server:build  
    
    # 使用docker cp命令从 builder 容器中拷贝 http-server 文件到当前构建目录
    # 并且删除名称为 builder 的临时容器。
    docker cp builder:/go/src/httpServer/http-server ./http-server  
    docker rm -f builder
    
    # 输出开始构建镜像信息。
    echo Building http-server:latest
    
    # 构建运行时镜像,然后删除临时文件 http-server
    docker build -t http-server:latest .
    rm ./http-server
    

    上面我们使用 Dockerfile.build 文件来编译应用程序,使用 Dockerfile 文件来构建应用的运行环境。然后我们通过创建一个临时容器,把编译后的 http-server 文件拷贝到当前构建目录中,然后再把这个文件拷贝到运行环境的镜像中,最后指定容器的启动命令为 http-server。

    使用这种方式虽然可以实现分离镜像的编译和运行环境,但是我们需要额外引入一个 build.sh 脚本文件,而且构建过程中,还需要创建临时容器 builder 拷贝编译后的 http-server 文件,这使得整个构建过程比较复杂,并且整个构建过程也不够透明。

    为了解决这种问题, Docker 在 17.05 推出了多阶段构建(multistage-build)的解决方案。

    2、使用多阶段构建

    Docker 允许我们在 Dockerfile 中使用多个FROM 语句,而每个 FROM 语句都可以使用不同基础镜像。最终生成的镜像,是以最后一条 FROM 为准,所以我们可以在一个 Dockerfile 中声明多个 FROM,然后选择性地将一个阶段生成的文件拷贝到另外一个阶段中,从而实现最终的镜像只保留我们需要的环境和文件。多阶段构建的主要使用场景是分离编译环境和运行环境。

    接下来我们使用多阶段构建将上述未使用多阶段构建的过程精简:

    # 编译,生成http-server
    FROM golang:1.15
    WORKDIR /go/src/httpServer/
    COPY main.go .
    RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
    
    # 构建运行时镜像
    FROM alpine:latest  
    WORKDIR /root/
    # --from=0 表示从第一阶段构建结果中拷贝文件到当前构建阶段
    COPY --from=0 /go/src/httpServer/http-server .  
    CMD ["./http-server"] 
    

    构建镜像:

    docker build -t http-server:latest .
    

    3、多阶段构建的其他使用方式

    3.1、为构建阶段命名

    默认情况下,每一个构建阶段都没有被命名,可以通过 FROM 指令出现的顺序来引用这些构建阶段,构建阶段的序号是从 0 开始的。然而,为了提高 Dockerfile 的可读性,我们需要为某些构建阶段起一个名称,这样即便后面我们对Dockerfile 中的内容进程重新排序或者添加了新的构建阶段,其他构建过程中的 COPY 指令也不需要修改。

    对上面的Dockerfile进行优化:

    # 编译,生成http-server
    FROM golang:1.15 AS builder
    WORKDIR /go/src/httpServer/
    COPY main.go .
    RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
    
    # 构建运行时镜像
    FROM alpine:latest  
    WORKDIR /root/
    COPY --from=builder /go/src/httpServer/http-server .  
    CMD ["./http-server"] 
    

    我们在第一个构建阶段,使用 AS 指令将这个阶段命名为 builder。然后在第二个构建阶段使用 --from=builder 指令,即可从第一个构建阶段中拷贝文件,使得 Dockerfile 更加清晰可读。

    3.2、停止在特定的构建阶段

    有时候,我们的构建阶段非常复杂,我们想在代码编译阶段进行调试,但是多阶段构建默认构建 Dockerfile 的所有阶段,为了减少每次调试的构建时间,我们可以使用 target 参数来指定构建停止的阶段。

    例如,我只想在编译阶段调试 Dockerfile 文件,可以使用如下命令:

    # docker build -t http-server:latest . --target builder
    
    Sending build context to Docker daemon  6.656kB
    Step 1/4 : FROM golang:1.15 AS builder
     ---> 40349a2425ef
    Step 2/4 : WORKDIR /go/src/httpServer/
     ---> Using cache
     ---> 90a929dd37de
    Step 3/4 : COPY main.go .
     ---> Using cache
     ---> fee18351e532
    Step 4/4 : RUN CGO_ENABLED=0 GOOS=linux go build -o http-server .
     ---> Using cache
     ---> e7356e46c9c4
    Successfully built e7356e46c9c4
    Successfully tagged http-server:latest
    

    3.3、使用现有镜像作为构建阶段

    使用多阶段构建时,不仅可以从 Dockerfile 中已经定义的阶段中拷贝文件,还可以使用COPY --from指令从一个指定的镜像中拷贝文件,指定的镜像可以是本地已经存在的镜像,也可以是远程镜像仓库上的镜像。

    例如,我们从上面编译环境镜像中拷贝出编译好的二进制文件

    # 构建运行时镜像
    FROM alpine:latest  
    WORKDIR /root/
    COPY --from=http-server:latest /go/src/httpServer/http-server .  
    CMD ["./http-server"] 
    

    使用下面命令进行构建:

    # docker build -t http-server:v2.0 . 
    
    Sending build context to Docker daemon   7.68kB
    Step 1/4 : FROM alpine:latest
     ---> c059bfaa849c
    Step 2/4 : WORKDIR /root/
     ---> Running in bc1e94677d94
    Removing intermediate container bc1e94677d94
     ---> ab8143431ed0
    Step 3/4 : COPY --from=http-server:latest /go/src/httpServer/http-server .
     ---> 133f25c0d3f0
    Step 4/4 : CMD ["./http-server"]
     ---> Running in d625e202db78
    Removing intermediate container d625e202db78
     ---> 00a4c8370bc9
    Successfully built 00a4c8370bc9
    Successfully tagged http-server:v2.0
    

    又例如,当我们想要拷贝 nginx 官方镜像的配置文件到我们自己的镜像中时,可以在 Dockerfile 中使用以下指令:

    COPY --from=nginx:latest /etc/nginx/nginx.conf /etc/local/nginx.conf
    
  • 相关阅读:
    常见面试题1
    勒索病毒防范方法
    VMware虚拟机打开后不兼容
    win10桌面显示我的电脑设置
    scala集合和Java集合对应转换操作
    scala中使用redis
    爬虫调研
    hadoop命令
    IDEA打jar包
    spark.mllib
  • 原文地址:https://www.cnblogs.com/huiyichanmian/p/15651413.html
Copyright © 2020-2023  润新知