3. Docker 构建
3.1. 建造者模式(流程有一定冗余,分步骤进行)
很常见的是有一个 Dockerfile 用于开发(其中包含构建应用程序所需的所有内容),以及一个用于生产的精简版 Dockerfile,它只包含您的应用程序以及运行它所需的内容。这被称为“建造者模式”。维护两个 Dockerfiles 并不理想,但一些特定情况下还是有用(比如构建环境的单独抽离)
- 通过第一个容器 build 出来 go 应用;
- 将 build 出来的 go 应用,copy 出来到当前目录;
- 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,可以构建指定的阶段,一些场景有需要:
- 调试指定的构建阶段镜像
- 使用 Debug 的调试阶段
- 使用 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 和容器一起使用,应用构建这块,有两种方式:
- Go 支持跨平台交叉编译,可以直接在开发机器上面 Build 目标机器的执行文件:
GOOS=linux go build -o hello hello.go
- 或者在 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. 参考
- Docker 概述:https://docs.docker.com/engine/docker-overview/
- 应用开发最佳实践:https://docs.docker.com/develop/dev-best-practices/
- 编写 Dockerfiles 的最佳实践:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
- 多阶段构建:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#use-multi-stage-builds
- golang docker hub: https://hub.docker.com/_/golang/