Docker (一) Brief Summary (Dockerfile、多阶段构建、镜像代理)

AI 摘要: 本文介绍了使用Docker进行构建的建造者模式,通过分步骤构建和精简Dockerfile实现构建应用程序的目的。同时提到了Docker-Compose、Docker-Swarm和Docker-Machine等内容。

3. Docker 构建

3.1. 建造者模式(流程有一定冗余,分步骤进行)

很常见的是有一个 Dockerfile 用于开发(其中包含构建应用程序所需的所有内容),以及一个用于生产的精简版 Dockerfile,它只包含您的应用程序以及运行它所需的内容。这被称为“建造者模式”。维护两个 Dockerfiles 并不理想,但一些特定情况下还是有用(比如构建环境的单独抽离)

  1. 通过第一个容器 build 出来 go 应用;
  2. 将 build 出来的 go 应用,copy 出来到当前目录;
  3. build 第二个容器,将 go 应用复制进去;

Dockerfile.build

1
2
3
4
5
6
// Dockerfile.build (Golang应用编译)
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY app.go .
RUN go get -d -v golang.org/x/net/html \
  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

Dockerfile(Golang 应用构建)

1
2
3
4
5
6
// Dockerfile(Golang应用构建)
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]

build.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// build.sh
// !/bin/sh
echo Building alexellis2/href-counter:build

docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
    -t alexellis2/href-counter:build . -f Dockerfile.build

docker container create --name extract alexellis2/href-counter:build
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app
docker container rm -f extract

echo Building alexellis2/href-counter:latest

docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app

3.2. 使用多阶段构建

对于多阶段构建,您可以 FROM 在 Dockerfile 中使用多个语句。每条 FROM 指令可以使用不同的镜像基础,并且每个指令都开始构建的新阶段。

最终结果是与以前相同的微小生产图像,复杂性显着降低。COPY --from=0行仅将前一阶段的构建工件复制到此新阶段。Go SDK 和任何中间工件都被遗忘,而不是保存在最终图像中

不需要创建任何中间图像,也不需要将任何工件提取到本地系统。

解决上述 Go 应用的分两步构建

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Dockerfile 多阶段构建
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
// 基于第一阶段构建结果
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

3.2.1. 指定命名构建阶段

默认情况下,第一个 FROM 镜像从 0 标识,可以通过AS <NAME>指示:

1
2
3
FROM golang:1.7.3 AS builder
...
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .

3.2.2. 指定命名构建阶段生成镜像

构建映像时,不一定需要构建整个 Dockerfile,可以构建指定的阶段,一些场景有需要:

  1. 调试指定的构建阶段镜像
  2. 使用 Debug 的调试阶段
  3. 使用 Testing 的测试阶段
1
docker build --target builder -t alexellis2/href-counter:latest .

3.2.3. COPY 还可以基于外部镜像

1
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

3.3. Golang 应用构建

Golang 和容器一起使用,应用构建这块,有两种方式:

  1. Go 支持跨平台交叉编译,可以直接在开发机器上面 Build 目标机器的执行文件:GOOS=linux go build -o hello hello.go
  2. 或者在 Docker 构建后,生成 Go 二进制应用(编译 GO 应用的环境可以复用)

3.3.1. 构建环境和应用未分离

使用 Go 容器作为构建和运行时环境,Docker 镜像包很大

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Dockerfile [未优化]
// BaseImage setting
FROM golang:1.12-alpine as builder
RUN  apk --no-cache add git curl tcpdump

// Env setting
ENV ServerName serverA
ENV ServerPort 8851

// Code copy
COPY /data/github.com/micro-lab/gohttp /data/gohttp

// Go build
WORKDIR /data/gohttp
RUN go get -d -v ./...
RUN go install -v ./cmd/$ServerName

// Run App
EXPOSE $ServerPort
CMD ["sh","-c","/go/bin/$ServerName"]

// 构建后查看镜像大小
$ docker images |grep micro
micro_server_b         latest              02e62a7f0f9e        2 days ago          409MB

// 实际的应用大小很小,主要内容在Go程序以及
/data/gohttp # ls -alh /go/bin/serverA
-rwxr-xr-x    1 root     root        7.2M Jul 26 09:50 /go/bin/serverA
/data/gohttp # du -sh /usr/local/go /go/pkg/
352.9M  /usr/local/go
38.1M   /go/pkg/

3.3.2. 构建环境和应用分离

基于多阶段构建 Golang 运行容器,将构建环境和应用分离

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 方式1:基于多阶段构建,构建环境没有充分利用
// go build envirment prepare
FROM golang:1.12-alpine as go-builder
LABEL Description="Build Golang Application" \
        Vendor="ACME Products" \
        Version="1.0"
ENV GOPROXY=https://goproxy.io
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk update && apk add git
WORKDIR /data/gohttp
COPY /gohttp .
// go app comipler
RUN go install -v ./cmd/...

// Run go app server
FROM alpine:latest
LABEL Description="Golang Application Running"
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk update && apk add --no-cache git curl tcpdump
COPY --from=go-builder /go/bin/* /data/go/bin/

// Runngin Envirment
ENV ServerName="ServerName"
EXPOSE 8851
EXPOSE 8852
CMD ["sh","-c","/data/go/bin/$ServerName"]

3.3.3. 利用建造者模式,将 Golang 编译环境独立出来

独立出来的编译环境,可以专职用于构建 Golang 应用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 方式2:
// go build envirment prepare
FROM golang:1.12-alpine
LABEL Description="Build Golang Application" \
        Vendor="ACME Products" \
        Version="1.0"
ENV GOPROXY=https://goproxy.io
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk update && apk add git
VOLUME ["/go"]
WORKDIR /data/gohttp
COPY /gohttp .

3.3.4. 交叉编译应用程序

1
2
3
4
5
6
7
$ docker run --rm -it -v "$PWD":/usr/src/myapp -w /usr/src/myapp golang:1.8 bash
$ for GOOS in darwin linux; do
>   for GOARCH in 386 amd64; do
>     export GOOS GOARCH
>     go build -v -o myapp-$GOOS-$GOARCH
>   done
> done

3.4. 基于环境变量,传递给容器运行

1
2
3
4
5
6
7
8
9
// 运行ServerA
$ docker run -e ServerName=serverA -e ServerPort=8851 --rm -P micro_server
// 运行ServerB
$ docker run -e ServerName=serverB -e ServerPort=8852 --rm -P micro_server

$ curl localhost:32773
{"SrvName":"Server A","GoodId":7001100,"GoodSn":"SKU2001","Picture":["a.jpg","b.jpg"]}
$ curl localhost:32774
{"SrvName":"ServerB","UserId":100,"UserName":"Clark"}

3.5. 未完

剩余 Docker-Composer、Docker-Swarm、Docker-Machine 几块分开整理

4. 参考

  1. Docker 概述:https://docs.docker.com/engine/docker-overview/
  2. 应用开发最佳实践:https://docs.docker.com/develop/dev-best-practices/
  3. 编写 Dockerfiles 的最佳实践:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
  4. 多阶段构建:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#use-multi-stage-builds
  5. golang docker hub: https://hub.docker.com/_/golang/