程式碼範例請看這邊
之前就有看到 Docker 推出 BuildKit 功能,這次跟大家介紹什麼是 BuildKit。現在部署編譯流程肯定都會用到 Docker,不管測試及部署都盡量在 Docker 內實現,來做到環境隔離,但是要怎麼縮短 Docker 在編譯 Image 時間,這又是另外的議題,本篇跟大家介紹一個實驗性的功能就是 BuildKit ,原始碼可以參考這邊 ,希望未來這實驗性的功能可以正式納入 Docker 官方,網路上其實可以找到很多方式來做 Docker Layer 的 Cache,我個人最常用的就是 --cache-from
機制,可以適用在任何 CI/CD 流程,詳細說明可以參考這篇『在 docker-in-docker 環境中使用 cache-from 提升編譯速度 』,下面使用到的程式碼都可以直接參考此 Repository ,我還是使用 Go 語言 當作參考範例。
教學影片 VIDEO
如果對於課程內容有興趣,可以參考底下課程。
如果需要搭配購買請直接透過 FB 聯絡我 ,直接匯款(價格再減 100 )
事前準備 由於 BuildKit 是實驗性的功能,預設安裝好 Docker 是不會啟動這功能。目前只有支援編譯 Linux 容器。請透過底下方式來啟動:
1
DOCKER_BUILDKIT=1 docker build .
下完指令後,你會發現整個 output 結果不太一樣了,介面變得比較好看,也看到每個 Layer 編譯的時間
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[+] Building 0.1 s (15 /15 ) FINISHED
=> [internal] load .dockerignore 0.0 s
=> => transferring context: 2 B 0.0 s
=> [internal] load build definition from Dockerfile 0.0 s
=> => transferring dockerfile: 545 B 0.0 s
=> [internal] load metadata for docker.io/library/golang:1.14 -alpine 0.0 s
=> [1 /10 ] FROM docker.io/library/golang:1.14 -alpine 0.0 s
=> [internal] load build context 0.0 s
=> => transferring context: 184 B 0.0 s
=> CACHED [2 /10 ] RUN apk add bash ca-certificates git gcc g++ libc-dev 0.0 s
=> CACHED [3 /10 ] WORKDIR /app 0.0 s
=> CACHED [4 /10 ] COPY go.mod . 0.0 s
=> CACHED [5 /10 ] COPY go.sum . 0.0 s
=> CACHED [6 /10 ] RUN go mod download 0.0 s
=> CACHED [7 /10 ] COPY main.go . 0.0 s
=> CACHED [8 /10 ] COPY foo/foo.go foo/ 0.0 s
=> CACHED [9 /10 ] COPY bar/bar.go bar/ 0.0 s
=> CACHED [10 /10 ] RUN go build -o /app -v -tags netgo -ldflags '-w -extldflags "-static"' . 0.0 s
=> exporting to image 0.0 s
=> => exporting layers 0.0 s
=> => writing image sha256:6 cc56539b3191d5efd87fb4d05181993d013411299b5cefb74047d2447b4d0c9 0.0 s
=> => naming to docker.io/appleboy/demo 0.0 s
如果要詳細的編譯步驟,請加上 --progress=plain
,就可以看到詳細的過程。其實我覺得重點在每個步驟都實際追加了時間,對於在開發上或者是 CI/CD 的流程上都相當有幫助。另外可以在 docker daemon 加上 config 就可以不用加上 DOCKER_BUILDKIT
環境變數
1
2
3
4
5
6
7
{
"debug" : true ,
"experimental" : true ,
"features" : {
"buildkit" : true
}
}
請記得重新啟動 Docker 讓新的設定生效。
不使用 BuildKit 編譯 這邊我們直接拿 Go 語言基本範例來測試看看到底省下多少時間,程式碼都可以在這裡找到 ,底下是範例:
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
32
33
34
35
package main
import (
"net/http"
"gin/bar"
"gin/foo"
"github.com/gin-gonic/gin"
)
func main () {
r := gin.Default ()
r.GET ("/ping" , func (c *gin.Context) {
c.JSON (http.StatusOK, gin.H{
"message" : "pong" ,
})
})
r.GET ("/ping2" , func (c *gin.Context) {
c.JSON (http.StatusOK, gin.H{
"message" : "pong2" ,
})
})
r.GET ("/ping100" , func (c *gin.Context) {
c.JSON (http.StatusOK, gin.H{
"message" : foo.Foo (),
})
})
r.GET ("/ping101" , func (c *gin.Context) {
c.JSON (http.StatusOK, gin.H{
"message" : bar.Bar (),
})
})
r.Run () // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
接著撰寫 Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FROM golang:1.14-alpine
LABEL maintainer ="Bo-Yi Wu <appleboy.tw@gmail.com>"
RUN apk add bash ca-certificates git gcc g++ libc-dev
WORKDIR /app
# Force the go compiler to use modules
ENV GO111MODULE =on
# We want to populate the module cache based on the go.{mod,sum} files.
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY main.go .
COPY foo/foo.go foo/
COPY bar/bar.go bar/
ENV GOOS =linux
ENV GOARCH =amd64
RUN go build -o /app -v -tags netgo -ldflags '-w -extldflags "-static"' .
CMD ["/app" ]
可以看到如果 go.mode 跟 go.sum 如果沒有任何變動,基本上 go module 檔案自然就可以透過 docker cache layer 處理。但是每次只要程式碼有任何異動,最後的 go build 會從無到有編譯,請看底下結果:
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
32
docker build --progress=plain -t appleboy/docker-demo -f Dockerfile .
#14 [10/10] RUN go build -o /app -v -tags netgo -ldflags '-w -extldflags "-s...
#14 0.391 gin/foo
#14 0.403 gin/bar
#14 0.412 github.com/go-playground/locales/currency
#14 0.438 github.com/gin-gonic/gin/internal/bytesconv
#14 0.441 github.com/go-playground/locales
#14 0.449 golang.org/x/sys/unix
#14 0.464 net
#14 0.471 github.com/gin-gonic/gin/internal/json
#14 0.508 github.com/go-playground/universal-translator
#14 0.511 github.com/leodido/go-urn
#14 0.694 github.com/golang/protobuf/proto
#14 0.754 gopkg.in/yaml.v2
#14 1.535 github.com/mattn/go-isatty
#14 1.789 net/textproto
#14 1.790 crypto/x509
#14 1.920 vendor/golang.org/x/net/http/httpproxy
#14 1.978 vendor/golang.org/x/net/http/httpguts
#14 2.019 github.com/go-playground/validator/v10
#14 2.434 crypto/tls
#14 3.043 net/http/httptrace
#14 3.085 net/http
#14 4.211 net/rpc
#14 4.212 github.com/gin-contrib/sse
#14 4.212 net/http/httputil
#14 4.372 github.com/ugorji/go/codec
#14 6.322 github.com/gin-gonic/gin/binding
#14 6.322 github.com/gin-gonic/gin/render
#14 6.517 github.com/gin-gonic/gin
#14 6.819 gin
#14 DONE 7.8s
總共花了 7.8 秒,但是各位想想,在自己電腦開發時,不會這麼久,而是會根據修正過的 Go 檔案才會進行編譯,但是在 CI/CD 流程怎麼做到呢?其實可以發現在電腦裡面都有 Cache 過已經編譯過的檔案。在 Linux 環境會是 /root/.cache/go-build
。那我們該如何透過 buildKit 加速編譯?
使用 BuildKit 編譯 先來看看在 Dockerfile 該如何改進才可以讓編譯加速?底下看看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# syntax = docker/dockerfile:experimental
FROM golang:1.14-alpine
LABEL maintainer ="Bo-Yi Wu <appleboy.tw@gmail.com>"
RUN --mount=type =cache,target=/var/cache/apk apk add bash ca-certificates git gcc g++ libc-dev
WORKDIR /app
# Force the go compiler to use modules
ENV GO111MODULE =on
# We want to populate the module cache based on the go.{mod,sum} files.
COPY go.mod .
COPY go.sum .
RUN --mount=type =cache,target=/go/pkg/mod go mod download
COPY main.go .
COPY foo/foo.go foo/
COPY bar/bar.go bar/
ENV GOOS =linux
ENV GOARCH =amd64
RUN --mount=type =cache,target=/go/pkg/mod --mount=type =cache,target=/root/.cache/go-build go build -o /app -v -tags netgo -ldflags '-w -extldflags "-static"' .
CMD ["/app" ]
首先看到第一行是務必要填寫
1
# syntax = docker/dockerfile:experimental
接著使用 --mount
方式進行檔案 cache,可以在任何 RUN
的步驟進行。所以可以看到在 go build 地方使用了:
1
2
RUN --mount=type =cache,target=/go/pkg/mod \
--mount=type =cache,target=/root/.cache/go-build
可以看到此步驟將 go module 及 build 後的檔案全部 cache 下來,這樣下次編譯的時候,就會自動將檔案預設放在對應的位置,加速編譯流程
1
2
3
4
5
docker build --progress=plain -t appleboy/docker-buildkit -f Dockerfile.buildkit .
#16 [stage-0 10/10] RUN --mount=type=cache,target=/go/pkg/mod --mount=type=c...
#16 0.381 gin/foo
#16 0.447 gin
#16 DONE 1.2s
可以看到修改了檔案後,編譯的結果跟在自己電腦上一模一樣,縮短了六秒時間,在大型的 Go 專案省下的時間可不少啊。
心得 現在 CI/CD 的工具不確定都有支持 docker buildKit,可能要自己做實驗試試看,像是現在 GitHub Action 官方也不支援 docker buildkit 。如果是全部自己架設的話,基本上可以完全使用 docker buildKit + docker cache-from 兩者一起用,相信會省下不少時間啊。程式碼範例請看這邊
See also